19

我参考这个SO question 来问这个问题。唐斯图尔特接受的答案:第一行说“您的代码是高度多态的,将所有浮点变量更改为 Double ..”,它提供了 4 倍的性能提升。

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

但是有些语言很好地利用了 ad-hoc 多态性来生成快速代码,为什么 GHC 不会或不能?(阅读 C++ 或 D)

为什么我们不能为 Haskell 提供像 blitz++ 或 eigen 这样的东西?我不明白 GHC 中的类型类和(临时)多态性是如何工作的。

4

3 回答 3

18

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

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

于 2013-09-17T16:25:36.923 回答
18

我想补充 Dirk 的回答,说INLINABLE通常建议超过SPECIALIZE. 函数上的INLINABLE注释可确保模块导出函数的原始源代码,以便在使用时对其进行专门化。这通常消除了SPECIALIZE为每个用例提供单独的编译指示的需要。

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

于 2013-09-17T18:14:48.027 回答
11

我不明白类型类在 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++ 模板一样。对于更大、更复杂的多态函数,可能会有一些成本。一般来说,编译器会决定这一点,而不是你 - 除非你想开始在这个地方洒 pragma。;-) 或者,如果你实际上不使用任何多态性,你可以只给所有单态类型签名......

于 2013-09-18T21:24:00.057 回答