懒惰
这不是“编译器优化”,而是语言规范保证的东西,所以你总是可以指望它发生。从本质上讲,这意味着在您对结果“做某事”之前不会执行工作。(除非你做了几件事中的一件来故意关闭懒惰。)
显然,这本身就是一个完整的主题,SO 已经有很多关于它的问题和答案。
以我有限的经验,让你的代码太懒或太严格会比我要谈论的任何其他事情产生更大的性能损失(在时间和空间上)......
严格性分析
懒惰是指避免工作,除非有必要。如果编译器可以确定“总是”需要给定结果,那么它就不会费心存储计算并在以后执行它;它会直接执行它,因为这样更有效。这就是所谓的“严格性分析”。
很明显,问题在于编译器不能总是检测到什么时候可以变得严格。有时您需要给编译器一些提示。(除了涉足核心输出之外,我不知道有任何简单的方法可以确定严格性分析是否完成了您认为的工作。)
内联
如果您调用一个函数,并且编译器可以判断您正在调用哪个函数,它可能会尝试“内联”该函数 - 即用函数本身的副本替换函数调用。函数调用的开销通常非常小,但内联通常可以实现其他不会发生的优化,因此内联可能是一个巨大的胜利。
仅当函数“足够小”(或者如果您添加专门要求内联的编译指示)时,函数才会被内联。此外,只有在编译器可以判断您正在调用的函数时,才能内联函数。编译器可能无法分辨出两种主要方式:
在后一种情况下,您可以使用{-# SPECIALIZE #-}
pragma 生成硬编码为特定类型的函数版本。例如,将为该类型{-# 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
编译指示。(顺便说一句,这非常强大。作为库作者,您可以使用它来重写客户端代码!)
元:
平衡所有事物,以及所有...