项目Euler prob 10使用haskell

时间:2016-05-05 08:07:19

标签: haskell optimization primes lazy-evaluation ghc

好的,所以我做了一个小修改,似乎对haskell产生了很大的不同。这是怎么回事:

我从项目euler为Prob 10实施了Eratosthenes筛选。这是怎么回事:

primesBelowN :: Integer -> [Integer]
primesBelowN n = 2:3:filter f [6*k+i | k <- [1..(n-1)`div`6], i <- [-1, 1]]
                 where f x = foldr g True [2..truncate(sqrt(fromInteger x))]
                             where g t ac = (x `rem` t /= 0) && ac

main = print $ sum $ primesBelowN 2000000

用ghc编译它,我的运行时间为8.95秒:

$ ghc -O3 010SummationOfPrimes.hs
$ time 010SummationOfPrimes
142913828922
8.739u 0.122s 0:08.95 98.8% 0+0k 2384+0io 1pf+0w

我认为我可以通过在g函数中利用haskell的惰性求值来优化代码。可以通过简单地将ac作为&&的第一个参数来完成(或者我认为),这样它就不会计算ac == False的不等式:

primesBelowN :: Integer -> [Integer]
primesBelowN n = 2:3:filter f [6*k+i | k <- [1..(n-1)`div`6], i <- [-1, 1]]
                 where f x = foldr g True [2..truncate(sqrt(fromInteger x))]
                             where g t ac = ac && (x `rem` t /= 0)

main = print $ sum $ primesBelowN 2000000

令人惊讶的是,它使程序 4X更慢!。现在,运行时间明显更大,为30.94秒:

$ ghc -O3 010SummationOfPrimes.hs
$ time 010SummationOfPrimes
142913828922
30.765u 0.157s 0:30.94 99.9%    0+0k 2384+0io 1pf+0w

我不知道出了什么问题......任何提示/建议/答案?提前谢谢!

修改

所以,当我推翻另一件事时,我正在玩这个。如果我只调整我的函数,看起来很容易陷入无限循环:

primesBelowN :: Integer -> [Integer]
primesBelowN n = 2:3:filter f [6*k+i | k <- [1..(n-1)`div`6], i <- [-1, 1]]
                 where f x = foldr g True [m | m <- [2..], 
                                               m <= truncate(sqrt(fromInteger x))]
                             where g t ac = (x `rem` t /= 0) && ac

main = print $ sum $ primesBelowN 2000000

在这种情况下,进程内存只是大量爆炸(80Gig,在我杀死它之前),没有任何输出:

$ ghc -O3 010SummationOfPrimes.hs
$ time 010SummationOfPrimes
^C20.401u 7.994s 0:28.42 99.8%  0+0k 2384+0io 1pf+0w

任何想法现在出了什么问题?

1 个答案:

答案 0 :(得分:2)

关于问题的第一部分,请注意foldr如何运作:

foldr g x0 [x1, x2, x3, .., xn] = g x1 (g x2 (g x3 (..(..(g xn x0)))..)

在我们的案例中,

foldr g True [2..truncate(sqrt(fromInteger x))]
      where g t ac = (x `rem` t /= 0) && ac

相当于:

foldr g True (map h [2..truncate(sqrt(fromInteger x))])
      where g t ac = t && ac
            h t = x `rem` t /= 0

相当于:

foldr (&&) True (map h [2..truncate(sqrt(fromInteger x))])
      where h t = x `rem` t /= 0

又相当于:

(h x1) && ((h x2) && ((h x3) &&(....((h xn) && True)))..)

请记住,我们正在处理一种懒惰的语言,其中评估由模式匹配引导。 &&仅在其第一个参数中是严格的,这意味着除非必要,否则不必生成第二个参数表达式,即使这样,它只需要继续直到(h x2)处于最外层。 因此,更多/适当/表示是这样的(部分伪代码):

(h x1) && {instructions to generate (h x2) && ((h x3) && (....((h xn) && True)))..)}

因此,内存要求计算完整&amp;&amp;表达大多是不变的。我们只需要保留(h x1)以及生成下一个值的说明。如果(h x1)False,我们会停止,否则我们将其丢弃并生成另一对值和指令。这显然不是haskell实际实现的方式,但足以作为模型。

现在,如果你切换参数顺序,&&必须首先评估第二个参数的表达式,其中&&将依次完全评估下一个子表达式,所以on,要求所有中间值保留在堆栈中,直到整个表达式完全展开并根据需要进行评估。还要注意,余数检查将以相反的顺序进行,使得该过程更加低效,因为与较小的素数相比,复合数不太可能是较大素数的倍数。因此,总运行时间和内存要求更差。

关于问题的第二个(编辑过的)部分,问题是您不再使用有限列表。对列表理解的限制用作过滤而不是限制。为了使foldr使用&&完成,您需要提供一个空列表(这对于无限列表的定义是不可能的)或模式匹配列表的单个元素谓词返回False。不幸的是,有些情况(x是素数),谓词不会返回False,而foldr将继续尝试模式匹配另一个元素。所有这一切都是徒劳的,因为在一个点后因为后卫而不会产生更多的元素。它失败的原因几乎与此失败相同:

head $ filter (const False) [1..]