好的,所以我做了一个小修改,似乎对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
任何想法现在出了什么问题?
答案 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..]