为什么多态性在哈斯克尔(GHC)中如此昂贵?
我在回答这个SO问题时问这个问题。 Don stewart接受的答案:第一行说“你的代码是高度多态的,将所有float vars改为Double ..”,它提供了4倍的性能提升。
我有兴趣在Haskell中进行矩阵计算,我应该让它成为编写高度单形代码的习惯吗?
但是有些语言很好地利用ad-hoc多态来生成快速代码,为什么GHC不会或者不会呢? (读C ++或D)
为什么我们不能在Haskell中使用闪电++或特征? 我不明白GHC中的类型类和(ad-hoc)多态是如何工作的。
我不明白类型类是如何在GHC中工作的。
好的,考虑这个功能:
linear :: Num x => x -> x -> x -> x
linear a b x = a*x + b
这需要三个数字作为输入,并返回一个数字作为输出。 该功能接受任何数字类型; 它是多态的。 GHC如何实现这一点? 那么,本质上编译器会创建一个“类字典”,它保存其中的所有类方法(在本例中为+
, -
, *
等)。此字典成为该函数的额外隐藏参数。 像这样的东西:
data NumDict x =
NumDict
{
method_add :: x -> x -> x,
method_subtract :: x -> x -> x,
method_multiply :: x -> x -> x,
...
}
linear :: NumDict x -> x -> x -> x -> x
linear dict a b x = a `method_multiply dict` x `method_add dict` b
无论何时调用该函数,编译器都会自动插入正确的字典 - 除非调用函数也是多态的,在这种情况下,它将自己接收到一个字典,所以只需传递该字典即可。
事实上,缺乏多态性的函数通常比没有函数查找更快,但是因为知道类型允许进行额外的优化。 例如,我们的多态linear
函数可用于数字,向量,函数,比率,复数,任何事物。 现在,如果编译器知道我们想要使用它,比如说Double
,那么现在所有的操作都变成了单个机器代码指令,所有的操作数都可以在处理器寄存器中传递,依此类推。 所有这些都产生了非常有效的代码。 即使它是Double
组件的复杂数字,我们也可以使它更好,更高效。 如果我们不知道我们会得到什么类型的,我们不能做任何这样的优化......这就是大部分速度差异通常来自哪里。
对于像线性这样的微小函数,很有可能每次调用它时都会内联,导致没有多态性开销和少量代码重复 - 就像C ++模板一样。 对于更大,更复杂的多态函数,可能会有一些成本。 一般来说,编译器决定这一点,而不是你 - 除非你想开始在这个地方散布杂注。 ;-)或者,如果你实际上没有使用任何多态,你可以给所有的单态类型签名......
通过多态代码,通常在代码大小和代码速度之间进行权衡。 您可以为每种类型生成一个单独版本的相同代码,从而生成更大的代码,也可以生成可以在多种类型上运行的单一版本,这些版本的运行速度会更慢。
模板的C ++实现选择赞成以增加代码大小为代价提高代码速度。 默认情况下,GHC采取相反的权衡。 但是,可以使用SPECIALIZE和INLINABLE编译指示让GHC为不同类型生成单独的版本。 这将导致多态代码具有与单形代码类似的速度。
我想补充Dirk的回答,说通常推荐INLINABLE
超过SPECIALIZE
。 函数上的INLINABLE
注释保证模块导出函数的原始源代码,以便它可以在使用点专用。 这通常不需要为每个用例提供单独的SPECIALIZE
编译指示。
与INLINE
不同, INLINABLE
不会改变GHC的优化启发式。 它只是说“请导出源代码”。
上一篇: why polymorphism is so costly in haskell(GHC)?
下一篇: Haskell, Hackage, GHC and productivity. How to solve a real example?