从 GHC 7.6 的 the docs:
[Y] 你通常一开始甚至不需要 SPECIALIZE 编译指示。在编译模块 M 时,GHC 的优化器(带 -O)会自动考虑在 M 中声明的每个顶级重载函数,并将其专门用于在 M 中调用它的不同类型。优化器还考虑每个导入的 INLINABLE 重载函数,并将它专门用于在 M 中调用它的不同类型。
和
此外,给定函数 f 的 SPECIALIZE pragma,GHC 将自动为 f 调用的任何类型类重载函数创建特化,如果它们与 SPECIALIZE pragma 在同一个模块中,或者它们是 INLINABLE;以此类推,传递。
因此,GHC 应该自动特化标记为 INLINABLE
没有 pragma 的 some/most/all(?) 函数,如果我使用显式 pragma,则特化是可传递的。我的问题是:auto-specialization 是可传递的吗?
具体来说,这是一个小例子:
主要.hs:
import Data.Vector.Unboxed as U
import Foo
main =
let y = Bar $ Qux $ U.replicate 11221184 0 :: Foo (Qux Int)
(Bar (Qux ans)) = iterate (plus y) y !! 100
in putStr $ show $ foldl1' (*) ans
Foo.hs:
module Foo (Qux(..), Foo(..), plus) where
import Data.Vector.Unboxed as U
newtype Qux r = Qux (Vector r)
-- GHC inlines `plus` if I remove the bangs or the Baz constructor
data Foo t = Bar !t
| Baz !t
instance (Num r, Unbox r) => Num (Qux r) where
{-# INLINABLE (+) #-}
(Qux x) + (Qux y) = Qux $ U.zipWith (+) x y
{-# INLINABLE plus #-}
plus :: (Num t) => (Foo t) -> (Foo t) -> (Foo t)
plus (Bar v1) (Bar v2) = Bar $ v1 + v2
GHC 专门化了对 plus
的调用,但不专门化了 Qux
Num
实例中的 (+)
,这会降低性能。
然而,一个明确的 pragma
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
如文档所示,导致 transitive 特化,因此 (+)
是特化的,代码速度提高了 30 倍(均使用 -O2
编译)。这是预期的行为吗?我是否应该只期望 (+)
通过显式 pragma 进行传递性特化?
更新
7.8.2 的文档没有改变,行为也是一样的,所以这个问题仍然是相关的。
plus
的等价物被 not 标记为 INLINABLE 和 2)simonpj 表示有一些内联使用票证代码,但我示例的核心显示没有内联函数(特别是,我无法摆脱第二个 Foo
构造函数,否则 GHC 内联的东西)。
plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2
以使 LHS 在调用站点完全应用时会发生什么?它是否被内联,然后专业化开始了?
plus
的调用,但实际上我得到了 less 特化:对 plus
的调用也不是特化的。我对此没有任何解释,但打算把它留给另一个问题,或者希望它会在这个问题的答案中得到解决。
简短的答案:
据我了解,这个问题的关键点如下:
“自动专业化是可传递的吗?”我是否应该只期望 (+) 使用显式的编译指示进行传递性专门化? (显然是有意的)这是 GHC 的错误吗?是否与文档不一致?
AFAIK,答案是否定的,大部分是肯定的,但还有其他方法,不。
代码内联和类型应用程序专业化是速度(执行时间)和代码大小之间的权衡。默认级别在不使代码膨胀的情况下获得了一些加速。选择更详尽的级别由程序员通过 SPECIALISE
pragma 自行决定。
解释:
优化器还考虑每个导入的 INLINABLE 重载函数,并将其专门用于在 M 中调用它的不同类型。
假设 f
是一个函数,其类型包括一个类型变量 a
,该类型变量受类型类 C a
约束。如果在 (a) 同一模块中的任何函数的源代码中使用该类型应用程序调用 f
,或者 ( b) 如果 f
标记为 INLINABLE
,则任何其他从 B
导入 f
的模块。因此,自动特化不是可传递的,它只涉及在 A
的源代码中为 导入和调用的 INLINABLE
函数。
在您的示例中,如果您按如下方式重写 Num
的实例:
instance (Num r, Unbox r) => Num (Qux r) where
(+) = quxAdd
quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
quxAdd 不是 Main 专门导入的。 Main 导入 Num (Qux Int) 的实例字典,该字典在 (+) 的记录中包含 quxAdd。但是,虽然字典是导入的,但字典中使用的内容却不是。
plus 不调用 quxAdd,它使用为 Num t 的实例字典中的 (+) 记录存储的函数。该字典由编译器在调用站点(在 Main 中)设置。