Performance 何时使用各种语言杂注和优化?

Performance 何时使用各种语言杂注和优化?,performance,optimization,haskell,pragma,Performance,Optimization,Haskell,Pragma,我对haskell有一点了解,但我总是不确定应该使用什么样的pragmas和优化,以及在哪里使用。像 比如何时使用专门化pragma以及它有什么性能提升 在哪里使用规则。我听到人们谈论一个特殊的规则不开枪?我们如何检查 什么时候使函数的参数严格,什么时候有帮助?我明白,严格参数将使参数的求值成为标准形式,那么为什么我不应该对所有函数参数增加严格性呢?我怎么决定 如何查看和检查程序中是否存在空间泄漏?构成空间泄漏的一般模式是什么 我如何判断是否有太多懒散的问题?我总是可以检查堆分析,但我想知道懒

我对haskell有一点了解,但我总是不确定应该使用什么样的pragmas和优化,以及在哪里使用。像

  • 比如何时使用
    专门化
    pragma以及它有什么性能提升
  • 在哪里使用
    规则
    。我听到人们谈论一个特殊的规则不开枪?我们如何检查
  • 什么时候使函数的参数严格,什么时候有帮助?我明白,严格参数将使参数的求值成为标准形式,那么为什么我不应该对所有函数参数增加严格性呢?我怎么决定
  • 如何查看和检查程序中是否存在空间泄漏?构成空间泄漏的一般模式是什么
  • 我如何判断是否有太多懒散的问题?我总是可以检查堆分析,但我想知道懒散造成伤害的一般原因、示例和模式是什么
有没有特别针对haskell的关于高级优化(包括高级优化和非常低级优化)的消息来源

比如何时使用
专门化
pragma以及它有什么性能提升

如果你有一个(类型类)多态函数,你可以让编译器专门化一个函数,并期望它经常在类的一个或几个实例中被调用

专业化消除了使用字典查找的地方,并且通常能够进一步优化,类成员函数通常可以内联,并且它们受到严格性分析的约束,这两种方法都可能带来巨大的性能提升。如果唯一可能的优化是消除双要素查找,那么收益通常不会很大

从GHC-7开始,为函数提供一个
{-#inlineable#-}
pragma可能更有用,它使其(几乎不变,执行了一些规范化和去糖化)源代码在接口文件中可用,因此函数可以专门化,甚至可以在调用站点进行内联

在哪里使用
规则
。我听到人们谈论一个特殊的规则不开枪?我们如何检查

您可以使用
-ddump rule firings
命令行选项检查触发了哪些规则。这通常会转储大量已触发的规则,因此您必须搜索一些自己的规则

你使用规则

  • 当您有一个针对特殊类型的更高效的函数版本时,例如

    {-# RULES
    "realToFrac/Float->Double"  realToFrac   = float2Double
      #-}
    
  • 对于特殊参数,某些函数可以替换为更有效的版本,例如

    {-# 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
      #-}
    
  • 当根据一般规律重写表达式时,可能会生成更好优化的代码,例如

    {-# RULES
    "map/map"  forall f g. (map f) . (map g) = map (f . g)
      #-}
    
在后一种样式中,大量使用
规则
,例如在库中的融合框架中,对于中的列表函数,使用规则实现不同类型的融合(
foldr/build
fusion)

什么时候使函数的参数严格,什么时候有帮助?我明白,严格参数将使参数的求值成为标准形式,那么为什么我不应该对所有函数参数增加严格性呢?我怎么决定

将参数设置为严格将确保其计算为弱头范式,而不是范式

您不会将所有参数都设置为严格参数,因为某些函数在某些参数中必须是非严格参数才能工作,而如果在所有参数中都设置为严格参数,则某些函数的效率较低

对于
partition
的第二个参数必须是非严格的才能在无限列表上工作,更一般的是
foldr
中使用的每个函数在第二个参数中必须是非严格的才能在无限列表上工作。在有限列表中,在第二个参数中使用非严格函数可以显著提高效率(
foldr(&&&&&)True(False:replicate(10^9)True)

如果你知道在做任何有价值的工作之前必须对论点进行评估,那么你就把论点严格化了。在许多情况下,GHC的严格性分析器可以自己完成这项工作,但当然不是全部

一个非常典型的例子是循环或尾部递归中的累加器,在这种情况下,增加严格性可以防止在途中生成巨大的thunk

我不知道在哪里增加严格的规定,对我来说这是一个经验问题,过一段时间你就会知道在哪些地方增加严格可能会有帮助,哪些地方会有伤害

根据经验,对小数据(如
Int
)进行求值是有意义的,但也有例外

如何查看和检查程序中是否存在空间泄漏?构成空间泄漏的一般模式是什么

第一步是使用
+RTS-s
选项(如果程序与启用的rtsopts链接)。这显示了总的内存使用量,您通常可以通过它判断是否存在泄漏。 通过使用
+RTS-hT
选项运行程序可以获得更多信息输出,该选项生成堆配置文件,有助于定位空间泄漏(此外,程序需要与启用的RTSOPT链接)

如果需要进一步分析,则需要在启用评测的情况下编译程序(
-rtsops-prof-fprof auto
,在较旧的GHC中,
-fprof auto
选项不可用,
-prof auto all
选项是最接近的对应项)

然后,使用各种配置文件选项运行它,并查看生成的堆配置文件

导致空间泄漏的两个最常见原因是

  • 太懒了
  • 太严格
第三位可能是不必要的共享,GHC很少消除常见的子表达式,但它偶尔会在不需要的地方共享长列表

为了找到泄漏的原因,我可以