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

Performance 提高Haskell性能的简单技巧(关于ProjectEuler问题)?,performance,haskell,Performance,Haskell,通过阅读和解决Euler项目的问题,我对编程和学习Haskell是个新手。当然,要提高这些问题的性能,最重要的是使用更好的算法。但是,我很清楚,还有其他简单且易于实现的方法可以提高性能。出现了一个粗略的搜索,并提供了以下提示: 使用ghc标志-O2和-fllvm 使用Int类型,而不是Integer,因为它是未装箱的(甚至是Integer而不是Int64)。这需要键入函数,而不是让编译器自行决定 使用rem而不是mod进行除法测试 在适当的时候使用 在递归函数中使用累加器(我相信是尾部递归优化

通过阅读和解决Euler项目的问题,我对编程和学习Haskell是个新手。当然,要提高这些问题的性能,最重要的是使用更好的算法。但是,我很清楚,还有其他简单且易于实现的方法可以提高性能。出现了一个粗略的搜索,并提供了以下提示:

  • 使用ghc标志-O2和-fllvm
  • 使用Int类型,而不是Integer,因为它是未装箱的(甚至是Integer而不是Int64)。这需要键入函数,而不是让编译器自行决定
  • 使用rem而不是mod进行除法测试
  • 在适当的时候使用
  • 在递归函数中使用累加器(我相信是尾部递归优化)
  • 回忆录(?)
(一个答案还提到了worker/wrapper转换,但这似乎相当先进。)

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

关于性能有一个重要的问题

一个相当常见的问题是太少(或太多)的严格性(这在上面的性能页面中列出的部分中有介绍)。太多的懒惰会导致大量的重击累积,太多的严格会导致太多的评估

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


此外,如所示,共享可以对性能产生巨大影响(在许多情况下,这可以被视为一种记忆形式)。

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

例如:


我认为,在欧拉问题中,你能做的最大的提高就是理解问题并消除不必要的计算。即使你不了解所有的事情,你也可以做一些小的修正,使你的程序运行速度提高一倍。假设你正在寻找高达1.000.000的素数,那么你当然可以做
过滤iPrime[1..1000000]
。但是如果你想一想,你就会意识到,上面没有偶数是素数,你已经去掉了(大约)一半的功。而不是做
[1,2]++过滤iPrime[3,5..999999]

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


欧拉计划主要是为了找到问题的巧妙算法解决方案。一旦有了正确的算法,微优化就很少成为问题,因为即使是简单的或解释的(例如Python或Ruby)实现也应该在速度限制内运行良好。你需要的主要技巧是理解惰性评估,这样你就可以避免重击。

同一位作者之前的幻灯片内容更广泛;这是一个案例研究。有相当多的重叠,但没有对我造成任何伤害。
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