繁体   English   中英

Eratosthenes F#筛的.NET优化

[英].NET Optimization of F# Sieve of Eratosthenes

我正在摆弄F#并使用FSI REPL我注意到我的初学者天真的Eratosthenes Sieve的两个稍微不同的实现之间存在巨大的效率差异。 第一个有额外的if:

let rec sieve max current pList =
    match current with
    | 2 -> sieve max (current + 1) (current::pList)
    | 3 -> sieve max (current + 2) (current::pList)
    | n when n < max ->
        if (n % 5 = 0) || (n % 3 = 0) then
            sieve max (current + 2) (current::pList)
        else if (pList |> (List.map (fun n -> current % n = 0)) |> List.contains true) then
            sieve max (current + 2) (pList)
        else
            sieve max (current + 2) (current::pList)
    | n when n >= max
        -> pList
    | _
        ->  printfn "Error: list length: %A, current: %A" (List.length pList) current
            [-1]

第二个没有:

let rec sieve max current pList =
    match current with
    | 2 -> sieve max (current + 1) (current::pList)
    | 3 -> sieve max (current + 2) (current::pList)
    | n when n < max ->
        if (pList |> (List.map (fun n -> current % n = 0)) |> List.contains true) then
            sieve max (current + 2) (pList)
        else
            sieve max (current + 2) (current::pList)
    | n when n >= max
        -> pList
    | _
        ->  printfn "Error: list length: %A, current: %A" (List.length pList) current
            [-1]

带有额外if分支的那个实际上更慢,尽管看起来它应该更快。 然后,我使用以下内容在REPL中计时两个实现:

#time sieve 200000 2 [] #time

并且在我的机器上看到,使用额外if语句的实现花了大约2分钟,而每次运行时没有花费大约1分钟。 这怎么可能? 通过添加一个处理3或5的倍数的if语句,它实际上比仅映射整个列表慢,然后查找素数列表中是否有数字的除数。 怎么样? F#是否经过优化处理列表?

如果在第一个筛子中,可能是一个捷径,实际上会改变行为。 它不是删除除以3和5的所有内容,而是实际添加它。 很容易看出你是否比较输出:

1st sieve: [19; 17; 15; 13; 11; 9; 7; 5; 3; 2]
2st sieve: [19; 17; 13; 11; 7; 5; 3; 2]

我假设你想要的是这样的:

if (n % 5 = 0) || (n % 3 = 0) then
    sieve max (current + 2) (pList)

但是,在这种情况下,它将不包括5(显然)。 所以正确的代码是

if (n <> 5 && n % 5 = 0) || (n <> 3 && n % 3 = 0) then
    sieve max (current + 2) (pList)

检查上面代码的性能 - 应该没问题。

额外的if执行额外的计算,但不会破坏执行流程,这会愉快地继续到第二个if 所以实际上,你只为你的函数添加了一些无用的计算。 难怪现在需要更长的时间!

你必须想到这样的事情:

if (a)
    return b;
if (x)
    return y;
else 
    return z;

好吧,这在F#中的工作方式不同于它在C#或Java中工作的方式,或者你在想什么。 F#没有“早退”。 没有“陈述”,一切都是表达,一切都有结果。

添加这样无用的计算实际上会产生警告。 如果你注意警告,你应该注意到一个说“ 这个值被丢弃 ”或其他一些。 编译器试图通过指向无用的函数调用来帮助你。

要解决此问题, if使用elif替换第二个if

if (n % 5 = 0) || (n % 3 = 0) then
    sieve max (current + 2) (current::pList)
elif (pList |> (List.map (fun n -> current % n = 0)) |> List.contains true) then
    sieve max (current + 2) (pList)
else
    sieve max (current + 2) (current::pList)

这将使第二个分支仅在第一个分支失败时执行。

PS来想一想, if没有else甚至不应该编译,因为它的结果类型无法确定。 我不确定那里发生了什么。

PPS List.map f |> List.contains true更好地表示为List.exists f 既短高效。

当然,列表不一定有效。 我曾经创建了一个函数来创建一个布尔数组,其中每个素数都是真的,每个非素数都是假的:

let sieveArray limit =
    let a = Array.create limit true
    let rec setArray l h s x =
        if l <= h then
            a.[l] <- x
            setArray (l + s) h s x
    a.[0] <- false; a.[1] <- false
    for i = 0 to a.Length - 1 do
        if a.[i]
        then setArray (i + i) (a.Length - 1) i false
    a

要获取实际素数的列表,您可以只映射索引,过滤生成的数组:

sieveArray limit
|> Seq.mapi (fun i x -> i, x)
|> Seq.filter snd
|> Seq.map fst
|> Seq.toList

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM