我对F#很新。我试图了解如何在F#中获得快速代码。为此,我尝试编写两种方法(IsPrime1
和IsPrime2
)进行基准测试。我的代码是:
// Learn more about F# at http://fsharp.net
open System
open System.Diagnostics
#light
let isDivisible n d = n % d = 0
let IsPrime1 n =
Array.init (n-2) ((+) 2) |> Array.exists (isDivisible n) |> not
let rec hasDivisor n d =
match d with
| x when x < n -> (n % x = 0) || (hasDivisor n (d+1))
| _ -> false
let IsPrime2 n =
hasDivisor n 2 |> not
let SumOfPrimes max =
[|2..max|] |> Array.filter IsPrime1 |> Array.sum
let maxVal = 20000
let s = new Stopwatch()
s.Start()
let valOfSum = SumOfPrimes maxVal
s.Stop()
Console.WriteLine valOfSum
Console.WriteLine("IsPrime1: {0}", s.ElapsedMilliseconds)
//////////////////////////////////
s.Reset()
s.Start()
let SumOfPrimes2 max =
[|2..max|] |> Array.filter IsPrime2 |> Array.sum
let valOfSum2 = SumOfPrimes2 maxVal
s.Stop()
Console.WriteLine valOfSum2
Console.WriteLine("IsPrime2: {0}", s.ElapsedMilliseconds)
Console.ReadKey()
IsPrime1
需要760毫秒,而IsPrime2
需要260毫秒才能获得相同的结果。
这里发生了什么以及如何让我的代码更快?
答案 0 :(得分:3)
在IsPrime2
中,您不构造一个巨大的数组,因此您可以避免分配,显式遍历和垃圾收集此数组。请记住,您在max-1
中调用IsPrime1 / IsPrime2函数SumOfPrimes
次,因此有许多此类数组的实例。避免创建显式数据结构可以用作优化技术。
以下是一些可以对您的代码进行的小优化。
1)要检查hasDivisors
中的除数,您只需要检查sqrt(n)
并跳过所有偶数。如果没有找到除数,则检查的数字为素数。
let rec hasDivisor2 n d =
match d with
| x when x <= int(sqrt(float n)) -> (n % x = 0) || (hasDivisor2 n (d+2))
| _ -> false
let IsPrime3 n =
n = 2 || (n%2 <> 0 && not (hasDivisor2 n 3))
2)对于SumOfPrimes
,你可以消除中间数组,也可以跳过所有偶数(它们无论如何都不能是素数)。
let sumOfPrimes isPrime max =
[|2..max|] |> Array.filter isPrime|> Array.sum
let sumOfPrimes2 isPrime max =
let mutable sum = 2L
for i in 3..2..max do
if isPrime i then
sum <- sum + int64 i
sum
3)我做了一个小改动,以便将isPrime作为参数传递。通过这种方式,您可以更轻松地测量代码:
let time fn =
let sw = new System.Diagnostics.Stopwatch()
sw.Start()
let f = fn()
sw.Stop()
printfn "Time taken: %.2f s" <| (float sw.ElapsedMilliseconds)/1000.0
f
let maxVal = 200000
let p2 = time (fun () -> sumOfPrimes IsPrime2 maxVal)
let p3 = time (fun () -> sumOfPrimes2 IsPrime3 maxVal)
sumOfPrimes2
新IsPrime3
函数的速度非常快。 maxVal = 200000
在我的机器上花了0.05秒,而原始版本需要7.45秒。
答案 1 :(得分:0)
速度差异的原因是慢速代码执行如下操作:
if n%a.[1] = 0 || n%a.[2]=0 ...
快速代码的确如此:
if n%2=0 || n%(2+1)=0 ...
在快速的情况下,我们不需要去记忆来获得下一个因素。我们还避免在快速情况下构建数组
这是我用来构建素数表的非常快速的F#代码(来自这个答案:https://stackoverflow.com/a/12014908/124259):
#time "on"
let limit = 1000000
//returns an array of all the primes up to limit
let table =
let table = Array.create limit true //use bools in the table to save on memory
let tlimit = int (sqrt (float limit)) //max test no for table, ints should be fine
let mutable curfactor = 1;
while curfactor < tlimit-2 do
curfactor <- curfactor+2
if table.[curfactor] then //simple optimisation
let mutable v = curfactor*2
while v < limit do
table.[v] <- false
v <- v + curfactor
let out = Array.create (100000) 0 //this needs to be greater than pi(limit)
let mutable idx = 1
out.[0]<-2
let mutable curx=1
while curx < limit-2 do
curx <- curx + 2
if table.[curx] then
out.[idx]<-curx
idx <- idx+1
out