懒惰
这不是“编译器优化”,但这是语言规范所保证的,因此您始终可以指望它的发生。本质上,这意味着直到您对结果“做某事”,才可以执行工作。(除非您执行以下其中一项操作来故意关闭惰性功能。)
显然,这本身就是一个完整的主题,因此SO已经对此有很多疑问和答案。
在我有限的经验,使您的代码太懒或太严具有极大较大的性能损失(时间和空间)比任何其他的东西,我要说说...
严格度分析
懒惰是指除非必要,否则就避免工作。如果编译器可以确定“始终”需要给定的结果,那么它将不必费心存储计算并在以后执行它;它只会直接执行它,因为这样效率更高。这就是所谓的“严格度分析”。
显然,要解决的问题是编译器无法始终检测何时可以使某些事情变得严格。有时您需要给编译器一些提示。(我不知道有什么简单的方法可以确定严格性分析是否已经完成了您认为的工作,而不是通过核心输出。)
内联
如果您调用一个函数,并且编译器可以告诉您要调用的函数,则它可能会尝试“内联”该函数-即用该函数本身的副本替换该函数调用。函数调用的开销通常很小,但是内联通常会使其他优化得以实现,否则本来就不会发生,因此内联可以是一个很大的胜利。
仅当函数“足够小”时(或添加专门要求内联的编译指示),才对函数进行内联。同样,仅当编译器可以告诉您要调用的函数时,才可以内联函数。编译器无法分辨的主要方式有两种:
在后一种情况下,您可以使用{-# SPECIALIZE #-}
编译指示来生成功能的版本,这些版本被硬编码为特定类型。例如,{-# SPECIALIZE sum :: [Int] -> Int #-}
将sum
为该Int
类型编译一个硬编码的版本,这意味着+
可以内嵌在该版本中。
不过请注意,sum
只有在编译器可以告知我们正在使用时,才会调用我们的新特殊功能Int
。否则,sum
将调用原始的多态。同样,实际的函数调用开销非常小。内联可以实现的其他优化是有益的。
常见子表达式消除
如果某个代码块两次计算出相同的值,则编译器可以将其替换为同一计算的单个实例。例如,如果您这样做
(sum xs + 1) / (sum xs + 2)
那么编译器可能会对此进行优化
let s = sum xs in (s+1)/(s+2)
您可能希望编译器将始终执行此操作。但是,显然在某些情况下,这可能会导致性能变差,而不是更好,因此GHC并不总是这样做。坦白说,我不太了解这背后的细节。但最重要的是,如果此转换对您来说很重要,那么手动进行并不难。(如果不重要,为什么还要担心呢?)
案例表达
考虑以下:
foo (0:_ ) = "zero"
foo (1:_ ) = "one"
foo (_:xs) = foo xs
foo ( []) = "end"
前三个方程式均检查列表是否为非空(以及其他内容)。但是三次检查同一件事是浪费的。幸运的是,编译器很容易将其优化为几个嵌套的case表达式。在这种情况下,
foo xs =
case xs of
y:ys ->
case y of
0 -> "zero"
1 -> "one"
_ -> foo ys
[] -> "end"
这相当不直观,但效率更高。由于编译器可以轻松进行此转换,因此您不必担心。只需以最直观的方式编写模式匹配即可;编译器非常擅长重新排序和重新安排它,以使其尽可能快。
融合
用于列表处理的标准Haskell习惯用法是将采用一个列表并生成新列表的函数链接在一起。典型的例子是
map g . map f
不幸的是,尽管懒惰保证跳过不必要的工作,但是中间列表树液性能的所有分配和取消分配。编译器将尝试消除这些中间步骤,而采用“融合”或“砍伐森林”的方法。
麻烦的是,这些功能大多数都是递归的。没有递归,将内联将所有功能压缩到一个大代码块中,在其上运行简化程序并生成没有中间列表的真正最佳代码,这将是一个基本练习。但是由于递归,这是行不通的。
您可以使用{-# RULE #-}
编译指示来修复某些问题。例如,
{-# RULES "map/map" forall f g xs. map f (map g xs) = map (f.g) xs #-}
现在,每当GHC map
应用于时map
,它就会将其压缩成一个遍历列表,从而消除中间列表。
麻烦的是,这仅适用map
于map
。还有许多其他可能性- map
其次是filter
,filter
其次是map
,等等。不是为每个解决方案手动编写解决方案,而是发明了所谓的“流融合”。这是一个更复杂的把戏,在此不再赘述。
它的长短是:这些都是程序员编写的特殊优化技巧。GHC本身对融合一无所知。全部在列表库和其他容器库中。因此,进行哪种优化取决于您的容器库的编写方式(或更现实的是,您选择使用哪些库)。
例如,如果您使用Haskell '98阵列,请不要期望任何形式的融合。但我知道该vector
库具有广泛的融合功能。都是关于库的;编译器仅提供RULES
编译指示。(顺便说一下,这是非常强大的。作为库作者,您可以使用它来重写客户端代码!)
元:
在所有事物之间保持平衡...