来自GHC 7.6的文档:

你通常一开始就不需要使用specialized语法。当编译模块M时,GHC的优化器(带-O)会自动考虑在M中声明的每个顶级重载函数,并针对在M中被调用的不同类型对其进行特殊化。优化器还会考虑每个导入的INLINABLE重载函数,并针对在M中被调用的不同类型对其进行特殊化。

and

此外,给定函数f的specialspecialpragma, GHC将自动为f调用的任何类型类重载函数创建专门化,如果它们与specialspecialpragma在同一个模块中,或者如果它们是INLINABLE;以此类推。

因此,GHC应该自动专门化一些/大多数/所有(?)标记为INLINABLE的函数而不使用pragma,如果我使用显式的pragma,专门化是可传递的。我的问题是: 自动专门化是可传递的吗?

具体来说,这里有一个小例子:

Main.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。:

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专门化对加号的调用,但对Qux Num实例不专门化(+),这会降低性能。

然而,一个显式的pragma

{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}

如文档所示,结果是传递专门化,所以(+)是专门化的,代码速度快了30倍(都是用-O2编译的)。这是预期的行为吗?我应该只期望(+)通过显式的pragma传递地特殊化吗?


更新

7.8.2的文档没有改变,行为是一样的,所以这个问题仍然是相关的。


当前回答

简短的答案:

就我的理解,这个问题的重点是:

"自动专门化是可传递的吗" 我应该只期望(+)通过显式的pragma传递地特殊化吗? (显然是故意的)这是GHC的错误吗?是否与文档不一致?

AFAIK,答案是否定的,大部分是肯定的,但也有其他方法,也有否定的。

代码内联和类型应用程序专门化是速度(执行时间)和代码大小之间的权衡。默认级别可以在不膨胀代码的情况下获得一些加速。选择一个更详尽的级别是留给程序员的裁量权通过SPECIALISE pragma。

解释:

优化器还考虑每个导入的INLINABLE重载函数,并针对在M中调用它的不同类型将其特殊化。

假设f是一个函数的类型包含一个类型变量约束类型类C。默认GHC专门f关于类型的应用程序(用一个代替t)如果f与这类应用程序的源代码(a)任何功能相同的模块,或者(b)如果f INLINABLE标记,然后其他模块,从b .因此,进口f auto-specialization不是传递,只有触动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中)设置。

其他回答

简短的答案:

就我的理解,这个问题的重点是:

"自动专门化是可传递的吗" 我应该只期望(+)通过显式的pragma传递地特殊化吗? (显然是故意的)这是GHC的错误吗?是否与文档不一致?

AFAIK,答案是否定的,大部分是肯定的,但也有其他方法,也有否定的。

代码内联和类型应用程序专门化是速度(执行时间)和代码大小之间的权衡。默认级别可以在不膨胀代码的情况下获得一些加速。选择一个更详尽的级别是留给程序员的裁量权通过SPECIALISE pragma。

解释:

优化器还考虑每个导入的INLINABLE重载函数,并针对在M中调用它的不同类型将其特殊化。

假设f是一个函数的类型包含一个类型变量约束类型类C。默认GHC专门f关于类型的应用程序(用一个代替t)如果f与这类应用程序的源代码(a)任何功能相同的模块,或者(b)如果f INLINABLE标记,然后其他模块,从b .因此,进口f auto-specialization不是传递,只有触动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中)设置。