正如其他人提到的,代码没有做同样的事情,您需要采用技术来确保在找到素数后停止内部循环。
此外,您正在将值打印到标准输出。当您进行 CPU 性能测试时,这通常是不希望的,因为大量时间可能会导致 I/O 扭曲测试结果。
无论如何,即使有一个公认的答案,我还是决定对此进行一些修改,以将不同的建议解决方案与我自己的一些解决方案进行比较。
性能运行在 .NET 4.7.1 上的 x64 模式下。
我比较了不同的 F# 解决方案以及我自己的一些变体:
Running 'Original(F#)' with 100000 (10512)...
... it took 14533 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'Original(C#)' with 100000 (10512)...
... it took 1343 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'Aaron' with 100000 (10512)...
... it took 5027 ms with (3, 1, 0) cc and produces 9592 GOOD primes
Running 'SteveJ' with 100000 (10512)...
... it took 1640 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'Dumetrulo1' with 100000 (10512)...
... it took 1908 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'Dumetrulo2' with 100000 (10512)...
... it took 970 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'Simple' with 100000 (10512)...
... it took 621 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'PushStream' with 100000 (10512)...
... it took 1627 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'Unstalling' with 100000 (10512)...
... it took 551 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'Vectors' with 100000 (10512)...
... it took 1076 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'VectorsUnstalling' with 100000 (10512)...
... it took 1072 ms with (0, 0, 0) cc and produces 9592 GOOD primes
Running 'BestAttempt' with 100000 (10512)...
... it took 4 ms with (0, 0, 0) cc and produces 9592 GOOD primes
-
Original(F#) - OP 的原始 F# 代码更改为不使用标准输出
-
Original(C#) - OP 的原始 C# 代码更改为不使用标准输出
-
Aaron - 使用 Seq 的惯用方法。正如所料Seq 和性能通常不能很好地结合在一起。
-
SteveJ - @SteveJ 试图模仿 F# 中的 C# 代码
-
Dumetrulo1 - @dumetrulo 在尾递归中实现了算法
-
Dumetrulo2 - @dumetrulo 通过步进 +2 而不是 +1 改进了算法(不需要检查偶数)。
-
Simple - 我尝试使用类似于 Dumetrulo2 的尾递归方法。
-
PushStream - 我尝试使用简单的推流(Seq 是拉流)
-
Unstalling - 如果使用的指令有延迟,我会尝试解除 CPU 的失速
-
Vectors - 我尝试使用 System.Numerics.Vectors 对每个操作进行多个除法(又名 SIMD)。不幸的是,向量库不支持mod,所以我不得不模仿它。
-
VectorsUnstalling - 我尝试通过卸载 CPU 来改进 Vectors。
-
BestAttempt - 与 Simple 类似,但在确定是否为素数时仅检查直到 sqrt n 的数字。
总结
- F# 循环没有
continue 也没有break。 F# 中的尾递归是 IMO 实现需要 break 的循环的更好方法。
- 在比较语言的性能时,应该比较可能的最佳性能还是比较惯用解决方案的性能?我个人认为最好的性能是正确的方法,但我知道人们不同意我的观点(我为 F# 写了一个mandelbrot version for benchmark the game,其性能与 C 相当,但它没有被接受,因为这种风格被认为是非惯用的F#)。
-
不幸的是,F# 中的
Seq 增加了显着的开销。即使开销不相关,我也很难让自己使用它。
- 现代 CPU 指令具有不同的吞吐量和延迟数。这意味着有时为了提高性能,需要在内循环中处理多个独立样本,以允许乱序执行单元重新排序程序以隐藏延迟。如果您的 CPU 具有超线程并且您在多个线程上运行算法,则超线程可以“自动”减轻延迟。
- 缺少
mod 向量阻止了尝试使用 SIMD 来获得优于非 SIMD 解决方案的任何性能。
- 如果我修改
Unstalling 尝试循环与 C# 代码相同的次数,最终结果是 F# 中的 1100 ms 与 C# 中的 1343 ms 相比。因此,可以使 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 |> int
module 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.toArray
module 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 limit
module 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 limit
module 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 limit
module 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))
|> toArray
module 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 limit
module 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 limit
module 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 limit
module 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