C++ 为什么多态性在haskell(GHC)中如此昂贵?

C++ 为什么多态性在haskell(GHC)中如此昂贵?,c++,haskell,matrix,monomorphism,C++,Haskell,Matrix,Monomorphism,我问这个问题是因为我的问题。 Don stewart接受的答案:第一行说“您的代码是高度多态的,将所有浮点变量更改为Double..”,这使性能提高了4倍 我对在Haskell中进行矩阵计算感兴趣,我是否应该养成编写高度单态代码的习惯 但有些语言很好地利用了即席多态性来生成快速代码,为什么GHC不会或不能?(读取C++或D)< /P> 为什么我们不能为Haskell提供类似blitz++或eigen的东西?我不明白GHC中的类型类和(即席)多态性是如何工作的。对于多态代码,通常在代码大小和代码速

我问这个问题是因为我的问题。 Don stewart接受的答案:第一行说“您的代码是高度多态的,将所有浮点变量更改为Double..”,这使性能提高了4倍

我对在Haskell中进行矩阵计算感兴趣,我是否应该养成编写高度单态代码的习惯

但有些语言很好地利用了即席多态性来生成快速代码,为什么GHC不会或不能?(读取C++或D)< /P>
为什么我们不能为Haskell提供类似blitz++或eigen的东西?我不明白GHC中的类型类和(即席)多态性是如何工作的。

对于多态代码,通常在代码大小和代码速度之间进行权衡。要么为将要操作的每种类型生成同一代码的单独版本,这将导致更大的代码;要么生成可以操作多种类型的单个版本,这将更慢


模板的C++实现选择以增加代码大小为代价提高代码速度。默认情况下,GHC采取相反的权衡。但是,可以让GHC使用SPECIALIZE和Inlineable pragmas为不同类型生成单独的版本。这将导致多态代码的速度类似于单态代码。

我想补充Dirk的答案,说
内联的
通常比
专门化的
更受推荐。函数上的
inlineable
注释保证模块导出函数的原始源代码,以便在使用时对其进行专门化。这通常不需要为每个用例提供单独的
专门化
杂注

内联
不同,
内联
不会改变GHC的优化启发式。它只是说“请导出源代码”

我不明白类型类在GHC中是如何工作的

OK,考虑这个函数:

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++模板。对于更大、更复杂的多态函数,可能会有一些成本。通常情况下,编译器决定这一点,而不是你——除非你想开始在这个地方喷洒杂注或者,如果你实际上没有使用任何多态性,你可以只给所有的东西单态类型签名…

看看SPECIALIZE pragma,它是为这类东西设计的。这是一个很好的问题。对于ghc来说,有一个标志告诉它将一切都专门化为单态实例是非常合理的。与一些人似乎认为的相反,这不会导致普通程序的代码爆炸。我们可以有类似blitz/Egenen的东西,总有一天我们会的:他还提到了TypeClass,值得一提的是TypeClass及其相关记录可能带来的开销carry@fedvasu-见:“SPECIALIZE的作用是生成(A)函数的专用版本和(b)重写规则[…],该规则将对非专用函数的调用重写为对专用函数的调用。“当然,如果有疑问,请检查生成的核心。@Fixnum我知道了,SPECIALIZE关键字在功能方面有效,并且有一些适用性规则,我认为它可以在模块级或.hs文件级工作。”。感谢一个小的函数,如<>代码>线性<代码>,它很可能每次调用时都被内联,结果没有多态性开销和少量的代码复制——就像C++模板一样。对于更大、更复杂的多态函数,可能会有一些成本。通常情况下,编译器决定这一点,而不是你——除非你想开始在这个地方喷洒杂注或者,如果你实际上没有使用任何多态性,你可以给所有的东西单态类型签名。。。