Haskell性能提高的简单提示(关于ProjectEuler问题)?

Simple tips for Haskell performance increases (on ProjectEuler problems)?

我对编程和学习哈斯克尔是个新手,通过阅读和研究项目Euler问题。当然,要提高这些问题的性能,最重要的是使用更好的算法。但是,我很清楚还有其他简单且易于实现的方法来提高性能。粗略的搜索会出现这个问题和这个问题,给出以下提示:

  • 使用GHC标志-O2和-FLLVM。
  • 使用类型int,而不是integer,因为它是未装箱的(或者甚至是integer而不是int64)。这需要键入函数,而不是让编译器随时决定。
  • 使用REM而不是mod进行分区测试。
  • 适当时使用Schwartzian转换。
  • 在递归函数中使用累加器(我相信是尾部递归优化)。
  • 记忆化(?)

(一个答案也提到了Worker/Wrapper转换,但这似乎相当高级。)

问题:在Haskell中,还可以进行哪些简单的优化来提高Project Euler样式问题的性能?是否有其他特定于haskell的(或特定于函数编程的?)可以用来帮助加速解决项目Euler问题的想法或功能?相反,一个人应该注意什么?要避免哪些常见但效率低下的事情?


下面是我经常提到的Johan Tibell的一些好幻灯片:

Haskell性能模式


一个简单的建议是使用hlint,它是一个检查源代码并提出改进语法建议的程序。这可能不会提高速度,因为很可能已经由编译器或懒惰的计算完成了。但在某些情况下,它可能会帮助编译器。更进一步,它将使您成为一个更好的haskell程序员,因为您将学习更好的方法来做事情,并且可能更容易理解您的程序并分析它。

示例来自http://community.haskell.org/~ndm/darcs/hlint/hlint.htm,例如:

1
2
3
4
5
darcs-2.1.2\src\CommandLine.lhs:94:1: Error: Use concatMap
Found:
  concat $ map escapeC s
Why not:
  concatMap escapeC s

1
2
3
4
5
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

我认为在ProjectEuler问题中可以做的最大的增加就是理解这个问题并删除不必要的计算。即使你不了解所有的事情,你可以做一些小的修复,这将使你的程序运行两倍的速度。假设你在寻找高达1000.000的素数,那么你当然可以做filter isPrime [1..1000000]。但是如果你想一想,那么你就可以意识到,上面没有偶数是素数,在那里你去掉了(大约)一半的功。而不是做[1,2] ++ filter isPrime [3,5..999999]


haskellwiki中有一个相当大的部分是关于性能的。

一个相当常见的问题是过少(或过多)的严格性(这在上面的性能页面的"一般技术"部分中列出的部分中进行了介绍)。太多的懒惰会导致大量的雷鸣积累,太多的严格会导致太多的评估。

在编写尾部递归函数(即带有累加器的函数)时,这些注意事项尤其重要;而且,在这一点上,根据函数的使用方式,尾部递归函数在haskell中的效率有时低于等效的非尾部递归函数,即使使用了最佳的严格性注释。

同样,正如最近的这个问题所证明的,共享可以对性能产生巨大的影响(在许多情况下,这可以被视为一种记忆形式)。


项目欧拉主要是寻找聪明的算法解决问题。一旦你有了正确的算法,微优化就很少是一个问题,因为即使是一个简单的或解释的(如python或ruby)实现也应该在速度约束下运行良好。你需要的主要技巧是理解懒惰的评估,这样你就可以避免雷鸣般的积累。