通过阅读和解决Project Euler问题,我是编程和学习Haskell的新手。当然,改善这些问题的性能最重要的是使用更好的算法。但是,我很清楚,还有其他简单易行的方法可以提高性能。粗略搜索this question和this question,提供以下提示:
(一个答案也提到了工人/包装器转换,但这似乎相当先进。)
问题:在Haskell中可以做出哪些其他简单的优化来提高Project Euler风格问题的性能?是否有任何其他Haskell特定的(或特定于函数编程的?)想法或特性可用于帮助加速Project Euler问题的解决方案?相反,应该注意什么?什么是常见但效率低下的事情要避免?
答案 0 :(得分:13)
以下是Johan Tibell的一些很好的幻灯片,我经常提到:
答案 1 :(得分:6)
一个简单的建议是使用hlint
这是一个检查源代码并提出改进语法建议的程序。这可能不会提高速度,因为很可能它已经由编译器或延迟评估完成。但在某些情况下,可能可以帮助编译器。更进一步,它将使您成为更好的Haskell程序员,因为您将学习更好的方法来做事,并且可能更容易理解您的程序并进行分析。
来自http://community.haskell.org/~ndm/darcs/hlint/hlint.htm的示例,例如:
darcs-2.1.2\src\CommandLine.lhs:94:1: Error: Use concatMap
Found:
concat $ map escapeC s
Why not:
concatMap escapeC s
和
darcs-2.1.2\src\Darcs\Patch\Test.lhs:306:1: Error: Use a more efficient monadic variant
Found:
mapM (delete_line (fn2fp f) line) old
Why not:
mapM_ (delete_line (fn2fp f) line) old
我认为您在Project Euler问题中可以做的最大的增加是理解问题并删除不必要的计算。即使你不理解一切,你也可以做一些小的修复,这将使你的程序运行速度的两倍。假设您正在寻找高达1.000.000的素数,那么您当然可以filter isPrime [1..1000000]
。但是如果你想一点,那么你就可以意识到这一点,上面没有偶数就是素数,你已经删除了(约)一半的工作。而是做[1,2] ++ filter isPrime [3,5..999999]
答案 2 :(得分:5)
关于表现有fairly large section of the Haskell wiki。
一个相当常见的问题是太少(或太多)严格(这由上面的性能页面的General techniques部分中列出的部分涵盖)。太多的懒惰导致大量的thunk积累,太严格会导致太多的评估。
在编写尾递归函数(即带累加器的函数)时,这些考虑因素尤其重要;并且,根据函数的使用方式,尾随递归函数在Haskell中的效率有时低于等效的非尾递归函数,即使使用最优严格注释也是如此。
此外,正如this recent question所示,共享可以对性能产生巨大影响(在许多情况下,这可以被视为一种记忆形式)。
答案 3 :(得分:3)
项目Euler主要是为这些问题找到聪明的算法解决方案。一旦你有了正确的算法,微优化很少成为问题,因为即使是简单的或解释的(例如Python或Ruby)实现也应该在速度限制内运行良好。你需要的主要技巧是理解延迟评估,这样你就可以避免重建。