jeck猫
正如其他人提到的,代码没有做同样的事情,您需要采用技术来确保在找到素数后停止内部循环。此外,您正在将值打印为标准输出。当您进行 CPU 性能测试时,这通常是不希望的,因为大量时间可能是 I/O 歪曲测试结果。无论如何,即使有一个公认的答案,我还是决定对此进行一些修补,以查看将不同的建议解决方案与我自己的一些解决方案进行比较。性能运行x64在 .NET 4.7.1 模式下。我比较了不同的建议 F# 解决方案以及我自己的一些变体:Running 'Original(F#)' with 100000 (10512)... ... it took 14533 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'Original(C#)' with 100000 (10512)... ... it took 1343 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'Aaron' with 100000 (10512)... ... it took 5027 ms with (3, 1, 0) cc and produces 9592 GOOD primesRunning 'SteveJ' with 100000 (10512)... ... it took 1640 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'Dumetrulo1' with 100000 (10512)... ... it took 1908 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'Dumetrulo2' with 100000 (10512)... ... it took 970 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'Simple' with 100000 (10512)... ... it took 621 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'PushStream' with 100000 (10512)... ... it took 1627 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'Unstalling' with 100000 (10512)... ... it took 551 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'Vectors' with 100000 (10512)... ... it took 1076 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'VectorsUnstalling' with 100000 (10512)... ... it took 1072 ms with (0, 0, 0) cc and produces 9592 GOOD primesRunning 'BestAttempt' with 100000 (10512)... ... it took 4 ms with (0, 0, 0) cc and produces 9592 GOOD primes Original(F#) - OP 的原始 F# 代码改为不使用 stdoutOriginal(C#) - OP 的原始 C# 代码改为不使用 stdoutAaron- 使用的惯用方法Seq。正如预期的那样Seq,性能通常不会很好地结合在一起。SteveJ - @SteveJ 试图在 F# 中模仿 C# 代码Dumetrulo1 - @dumetrulo 在尾递归中实现了算法Dumetrulo2 - @dumetrulo 通过步进 +2 而不是 +1 来改进算法(不需要检查偶数)。Simple- 我尝试使用Dumetrulo2与尾递归类似的方法。PushStream- 我尝试使用简单的推流(Seq是拉流)Unstalling - 如果使用的指令有延迟,我尝试尝试使 CPU 停止运行Vectors- 我尝试使用System.Numerics.Vectors每个操作(又名 SIMD)进行多个除法。不幸的是,vector libary 不支持,mod所以我不得不模仿它。VectorsUnstalling- 我试图Vectors通过尝试停止 CPU来改进。BestAttempt- 喜欢Simple但只sqrt n在确定是否为素数时检查数字。包起来F# 循环没有也continue没有break. F# 中的尾递归是 IMO 实现需要break.在比较语言的性能时,应该比较最佳性能还是比较惯用解决方案的性能?我个人认为最好的性能是正确的方法,但我知道人们不同意我的意见(我写了一个mandelbrot 版本来对 F#的游戏进行基准测试,其性能与 C 相当,但它没有被接受,因为这种风格被视为非-F# 的惯用语)。Seq不幸的是,在 F# 中会增加大量开销。即使开销不相关,我也很难让自己使用它。现代 CPU 指令具有不同的吞吐量和延迟数量。这意味着有时为了提高性能,需要在内循环中处理多个独立样本,以允许乱序执行单元重新排序程序以隐藏延迟。如果您的 CPU 具有超线程并且您在多个线程上运行算法,则超线程可以“自动”减轻延迟。mod向量的缺乏阻止了使用 SIMD 来获得优于非 SIMD 解决方案的任何性能的尝试。如果我修改Unstalling尝试以与 C# 代码相同的次数循环,则最终结果1100 ms在 F# 中与1343 ms在 C# 中相比。因此,F# 可以与 C# 非常相似地运行。如果应用更多的技巧,它只需要,4 ms但对于 C# 也是一样的。无论如何,从几乎15 sec到4 ms.希望它对某人很有趣完整源代码:module Common = open System open System.Diagnostics let now = let sw = Stopwatch () sw.Start () fun () -> sw.ElapsedMilliseconds let time i a = let inline cc i = GC.CollectionCount i let ii = i () GC.Collect (2, GCCollectionMode.Forced, true) let bcc0, bcc1, bcc2 = cc 0, cc 1, cc 2 let b = now () let v = a ii let e = now () let ecc0, ecc1, ecc2 = cc 0, cc 1, cc 2 v, (e - b), ecc0 - bcc0, ecc1 - bcc1, ecc2 - bcc2 let limit = 100000 // pi(x) ~= limit/(ln limit - 1) // Using pi(x) ~= limit/(ln limit - 2) to over-estimate let estimate = float limit / (log (float limit) - 1.0 - 1.0) |> round |> intmodule Original = let primes limit = let ra = ResizeArray Common.estimate let mutable isPrime = true for i in 2 .. limit do for j in 2 .. i do if i <> j && i % j = 0 then isPrime <- false if isPrime then ra.Add i isPrime <- true ra.ToArray ()module SolutionAaron = let primes limit = {2 .. limit} |> Seq.filter (fun i -> {2 .. i-1} |> Seq.forall (fun j -> i % j <> 0)) |> Seq.toArraymodule SolutionSteveJ = let primes limit = let ra = ResizeArray Common.estimate let mutable loop = true for i in 2 .. limit do let mutable j = 2 while loop do if i <> j && i % j = 0 then loop <- false else j <- j + 1 if j >= i then ra.Add i loop <- false loop <- true ra.ToArray ()module SolutionDumetrulo1 = let rec isPrimeLoop (ra : ResizeArray<_>) i j limit = if i > limit then ra.ToArray () elif j > i then ra.Add i isPrimeLoop ra (i + 1) 2 limit elif i <> j && i % j = 0 then isPrimeLoop ra (i + 1) 2 limit else isPrimeLoop ra i (j + 1) limit let primes limit = isPrimeLoop (ResizeArray Common.estimate) 2 2 limitmodule SolutionDumetrulo2 = let rec isPrimeLoop (ra : ResizeArray<_>) i j limit = let incr x = if x = 2 then 3 else x + 2 if i > limit then ra.ToArray () elif j > i then ra.Add i isPrimeLoop ra (incr i) 2 limit elif i <> j && i % j = 0 then isPrimeLoop ra (incr i) 2 limit else isPrimeLoop ra i (incr j) limit let primes limit = isPrimeLoop (ResizeArray Common.estimate) 2 2 limitmodule SolutionSimple = let rec isPrime i j k = if i < k then (j % i) <> 0 && isPrime (i + 2) j k else true let rec isPrimeLoop (ra : ResizeArray<_>) i limit = if i < limit then if isPrime 3 i i then ra.Add i isPrimeLoop ra (i + 2) limit else ra.ToArray () let primes limit = let ra = ResizeArray Common.estimate ra.Add 2 isPrimeLoop ra 3 limitmodule SolutionPushStream = type Receiver<'T> = 'T -> bool type PushStream<'T> = Receiver<'T> -> bool module Details = module Loops = let rec range e r i = if i <= e then if r i then range e r (i + 1) else false else true open Details let range s e : PushStream<int> = fun r -> Loops.range e r s let filter p (t : PushStream<'T>) : PushStream<'T> = fun r -> t (fun v -> if p v then r v else true) let forall p (t : PushStream<'T>) : bool = t p let toArray (t : PushStream<'T>) : _ [] = let ra = ResizeArray 16 t (fun v -> ra.Add v; true) |> ignore ra.ToArray () let primes limit = range 2 limit |> filter (fun i -> range 2 (i - 1) |> forall (fun j -> i % j <> 0)) |> toArraymodule SolutionUnstalling = let rec isPrime i j k = if i + 6 < k then (j % i) <> 0 && (j % (i + 2)) <> 0 && (j % (i + 4)) <> 0 && (j % (i + 6)) <> 0 && isPrime (i + 8) j k else true let rec isPrimeLoop (ra : ResizeArray<_>) i limit = if i < limit then if isPrime 3 i i then ra.Add i isPrimeLoop ra (i + 2) limit else ra.ToArray () let primes limit = let ra = ResizeArray Common.estimate ra.Add 2 ra.Add 3 ra.Add 5 ra.Add 7 ra.Add 11 ra.Add 13 ra.Add 17 ra.Add 19 ra.Add 23 isPrimeLoop ra 29 limitmodule SolutionVectors = open System.Numerics assert (Vector<int>.Count = 4) type I4 = Vector<int> let inline (%%) (i : I4) (j : I4) : I4 = i - (j * (i / j)) let init : int [] = Array.zeroCreate 4 let i4 v0 v1 v2 v3 = init.[0] <- v0 init.[1] <- v1 init.[2] <- v2 init.[3] <- v3 I4 init let i4_ (v0 : int) = I4 v0 let zero = I4.Zero let one = I4.One let two = one + one let eight = two*two*two let step = i4 3 5 7 9 let rec isPrime (i : I4) (j : I4) k l = if l + 6 < k then Vector.EqualsAny (j %% i, zero) |> not && isPrime (i + eight) j k (l + 8) else true let rec isPrimeLoop (ra : ResizeArray<_>) i limit = if i < limit then if isPrime step (i4_ i) i 3 then ra.Add i isPrimeLoop ra (i + 2) limit else ra.ToArray () let primes limit = let ra = ResizeArray Common.estimate ra.Add 2 ra.Add 3 ra.Add 5 ra.Add 7 ra.Add 11 ra.Add 13 ra.Add 17 ra.Add 19 ra.Add 23 isPrimeLoop ra 29 limitmodule SolutionVectorsUnstalling = open System.Numerics assert (Vector<int>.Count = 4) type I4 = Vector<int> let init : int [] = Array.zeroCreate 4 let i4 v0 v1 v2 v3 = init.[0] <- v0 init.[1] <- v1 init.[2] <- v2 init.[3] <- v3 I4 init let i4_ (v0 : int) = I4 v0 let zero = I4.Zero let one = I4.One let two = one + one let eight = two*two*two let sixteen = two*eight let step = i4 3 5 7 9 let rec isPrime (i : I4) (j : I4) k l = if l + 14 < k then // i - (j * (i / j)) let i0 = i let i8 = i + eight let d0 = j / i0 let d8 = j / i8 let n0 = i0 * d0 let n8 = i8 * d8 let r0 = j - n0 let r8 = j - n8 Vector.EqualsAny (r0, zero) |> not && Vector.EqualsAny (r8, zero) |> not && isPrime (i + sixteen) j k (l + 16) else true let rec isPrimeLoop (ra : ResizeArray<_>) i limit = if i < limit then if isPrime step (i4_ i) i 3 then ra.Add i isPrimeLoop ra (i + 2) limit else ra.ToArray () let primes limit = let ra = ResizeArray Common.estimate ra.Add 2 ra.Add 3 ra.Add 5 ra.Add 7 ra.Add 11 ra.Add 13 ra.Add 17 ra.Add 19 ra.Add 23 isPrimeLoop ra 29 limitmodule SolutionBestAttempt = let rec isPrime i j k = if i < k then (j % i) <> 0 && isPrime (i + 2) j k else true let inline isqrt i = (i |> float |> sqrt) + 1. |> int let rec isPrimeLoop (ra : ResizeArray<_>) i limit = if i < limit then if isPrime 3 i (isqrt i) then ra.Add i isPrimeLoop ra (i + 2) limit else ra.ToArray () let primes limit = let ra = ResizeArray Common.estimate ra.Add 2 isPrimeLoop ra 3 limit[<EntryPoint>]let main argv = let testCases = [| "Original" , Original.primes "Aaron" , SolutionAaron.primes "SteveJ" , SolutionSteveJ.primes "Dumetrulo1" , SolutionDumetrulo1.primes "Dumetrulo2" , SolutionDumetrulo2.primes "Simple" , SolutionSimple.primes "PushStream" , SolutionPushStream.primes "Unstalling" , SolutionUnstalling.primes "Vectors" , SolutionVectors.primes "VectorsUnstalling" , SolutionVectors.primes "BestAttempt" , SolutionBestAttempt.primes |] do // Warm-up printfn "Warm up" for _, a in testCases do for i = 0 to 100 do a 100 |> ignore do let init () = Common.limit let expected = SolutionSimple.primes Common.limit for testCase, a in testCases do printfn "Running '%s' with %d (%d)..." testCase Common.limit Common.estimate let actual, time, cc0, cc1, cc2 = Common.time init a let result = if expected = actual then "GOOD" else "BAD" printfn " ... it took %d ms with (%d, %d, %d) cc and produces %d %s primes" time cc0 cc1 cc2 actual.Length result 0