当你有一个(类型类)多态函数,并且期望它在类的一个或多个实例中被频繁调用时,你可以让编译器对其进行特化。
特化会移除使用处的字典查找,并且通常能够进一步优化,类成员函数通常可以被内联,而且它们会受到严格性分析的影响,这两者都可能带来巨大的性能提升。如果唯一可能的优化是消除字典查找,那么收益通常不会很大。
截至GHC-7,更有用的做法可能是给函数加上
{-# INLINABLE #-}
pragma,使其(几乎不变,只进行了一些规范化和解糖)源代码在接口文件中可用,因此函数可以被特化,甚至可能在调用点内联。
人们谈论特定规则未触发时应该在哪里使用
RULES
。我们如何检查?
您可以使用
-ddump-rule-firings
命令行选项来检查已触发的规则。通常会转储大量已触发的规则,因此您需要搜索一下自己的规则。
您使用规则。
when you have a more efficient version of a function for special types, e.g.
{-# RULES
"realToFrac/Float->Double" realToFrac = float2Double
#-}
when some functions can be replaced with a more efficient version for special arguments, e.g.
{-# RULES
"^2/Int" forall x. x ^ (2 :: Int) = let u = x in u*u
"^3/Int" forall x. x ^ (3 :: Int) = let u = x in u*u*u
"^4/Int" forall x. x ^ (4 :: Int) = let u = x in u*u*u*u
"^5/Int" forall x. x ^ (5 :: Int) = let u = x in u*u*u*u*u
"^2/Integer" forall x. x ^ (2 :: Integer) = let u = x in u*u
"^3/Integer" forall x. x ^ (3 :: Integer) = let u = x in u*u*u
"^4/Integer" forall x. x ^ (4 :: Integer) = let u = x in u*u*u*u
"^5/Integer" forall x. x ^ (5 :: Integer) = let u = x in u*u*u*u*u
#-}
when rewriting an expression according to general laws might produce code that's better to optimise, e.g.
{-# RULES
"map/map" forall f g. (map f) . (map g) = map (f . g)
#-}
在融合框架中,例如在
text
库中以及
base
中的列表函数中广泛使用后一种风格的
RULES
。另一种不同类型的融合(
foldr/build
融合)也是使用规则实现的。
何时使函数参数严格,这样做有什么帮助?我知道使参数严格会使参数被计算为正常形式,那么为什么我不应该将严格性添加到所有函数参数中?我如何决定是否要使参数严格?
使参数严格将确保其被计算为弱头正常形式,而不是正常形式。
您不会使所有参数都严格,因为某些函数必须在某些参数中是非严格的才能正常工作,有些函数如果在所有参数中都是严格的,则效率较低。
对于 example,partition
必须在其第二个参数上是非严格的才能在无限列表上工作,更一般地,foldr
中使用的每个函数都必须在第二个参数上是非严格的才能在无限列表上工作。在有限列表上,使函数在第二个参数上非严格化可能会使它变得极其高效(foldr (&&) True (False:replicate (10^9) True)
)。
如果您知道该参数在可行的工作之前必须被评估,则可以使参数强制执行。在许多情况下,GHC 的严格性分析器可以自行完成此操作,但当然不是所有情况都适用。
一个非常典型的案例是循环或尾递归中的累加器,其中添加严格性可以防止在路上构建巨大的thunk。
我没有硬性规定应该在哪里添加严格性,对我来说这是一种经验问题,经过一段时间后,您会学习到在哪些地方添加严格性可能有帮助,在哪些地方可能有害。
作为经验法则,保持小数据(例如Int
)保持评估状态是有意义的,但也有例外情况。
我如何查看并检查程序中是否存在空间泄漏?构成空间泄漏的一般模式是什么?
第一步是使用+RTS -s选项(如果程序启用了rtsopts)。这将显示整个程序使用了多少内存,您通常可以通过此来判断是否存在泄漏。
更详细的输出可以通过使用+RTS -hT选项运行程序来获得,该选项生成堆文件,有助于定位空间泄漏(还需要链接启用了rtsopts的程序)。
如果需要进一步分析,则需要启用编译时的profiling(-rtsops -prof -fprof-auto,在旧版本的GHC中,-fprof-auto选项不可用,-prof-auto-all选项是最接近的对应项)。
然后您可以使用各种profiling选项运行它,并查看生成的堆文件。
空间泄漏的两个最常见原因是
1. 过度惰性
2. 过度严格
第三个原因可能是不必要的共享, GHC 很少进行公共子表达式消除,但它偶尔会共享长列表,即使不需要。
对于找到泄漏原因,我再次不知道硬性规定,有时,在一个地方添加严格性或在另一个地方添加惰性可以修复泄漏。
“如何查看是否存在过多惰性的问题?我总是可以检查堆分析,但我想知道惰性伤害的一般原因、示例和模式。”
通常,惰性是希望结果可以逐步构建的地方,而在没有处理完成之前无法传递结果的地方(例如在左折叠或通常在尾递归函数中)是不需要的。