GHC自动专业化的传递性 [英] Transitivity of Auto-Specialization in GHC
问题描述
通过文档对于GHC 7.6:
[Y]你通常甚至不需要SPECIALIZE编译指示。在编译模块M时,GHC的优化器(带-O)会自动考虑在M中声明的每个顶层重载函数,并将其专用于M中调用的不同类型。优化器还会考虑每个导入的INLINABLE重载函数,并专门针对M中所称的不同类型。
和
此外,给定一个函数f的SPECIALIZE pragma,如果它们与SPECIALIZE pragma位于同一模块中,GHC将自动为任何由f调用的类重载函数创建特化,或者如果他们是可以插入的;所以GHC应该自动专门化一些/ most / all(?)函数标记为
INLINABLE
,而不是一个附注,如果我使用明确的附注,专业化是传递性的。我的问题是:
是 auto -specialization transitive?
具体来说,这里有个小例子:
Main.hs:
导入Data.Vector.Unboxed为U
导入Foo
$ b $ 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)其中
导入Data.Vector.Unboxed为U
newtype Qux r = Qux(Vector r)
- 如果我移除了刘海或Baz构造函数,GHC就会加上`plus`
data Foo t = Bar!t
| Baz!t
实例(Num r,Unbox r)=> Num(Qux r)其中
{ - #INLINABLE(+)# - }
(Qux x)+(Qux y)= Qux $ U.zipWith(+)xy
{ - #INLINABLE plus# - }
plus ::(Num t)=> (Foo t) - > (Foo t) - > (Foo t)
plus(Bar v1)(Bar v2)= Bar $ v1 + v2
< GHC专门调用加
,但不是特化(+) in Qux
Num
实例会杀死性能。
然而,一个明确的附注
{ - #SPECIALIZE plus: :Foo(Qux Int) - > Foo(Qux Int) - > Foo(Qux Int)# - }
导致传递特化,因为docs指出,所以
(+)
是专用的,代码速度要快30倍(用-O2
编译)。这是预期的行为?我是否应该只希望(+)
通过一个明确的编译指示进行专门的传递?
UPDATE
7.8.2的文档没有改变,行为也是一样的,所以这个问题是仍然相关。 问题的要点,据我了解,它们如下:
- 是自动专业化可传递的吗? li>
- 我是否应该只希望(+)通过明确的附注来传递?
- (显然是有意的)这是GHC的错误吗?是否与文档不一致?
AFAIK,答案是否,主要是,但有其他方式,并且不会。
代码内联和类型应用程序专门化是速度(执行时间)和代码大小之间的折中。默认级别可以获得一些加速而不会膨胀代码。通过
SPECIALIZE
编译指示选择更详尽的级别由程序员决定。
说明:
优化器还会考虑每个导入的INLINABLE重载函数,并专门针对M中调用的不同类型。
假设
f
是一个函数,其类型包含一个类型变量a
被类型类C a
约束。 GHC在默认情况下专门针对类型应用程序f
(用a
代替t < (a)同一模块中的任何函数的源代码中调用该类型应用程序时调用
f
,或者(b)如果f
标记为INLINABLE
,那么任何其他导入f
来自
B
。因此,自动专门化不是传递的,它只触及A的源代码中导入和调用的
INLINABLE
。
在你的例子中,如果你重写
Num
的实例如下:instance(Num r,Unbox r)=> Num(Qux r)其中
(+)= quxAdd
quxAdd(Qux x)(Qux y)= Qux $ U.zipWith(+)xy
quxAdd
没有被主要
。Main
导入Num(Qux Int)
的实例字典,该字典包含quxAdd
在(+)
的记录中。但是,尽管字典已导入,但字典中使用的内容却不是。
plus
不会调用quxAdd
,它使用存储的函数(+)
记录在Num t
的实例字典中。该字典在编译器的调用站点(Main
)中设置。
From the docs for GHC 7.6:
[Y]ou often don't even need the SPECIALIZE pragma in the first place. When compiling a module M, GHC's optimiser (with -O) automatically considers each top-level overloaded function declared in M, and specialises it for the different types at which it is called in M. The optimiser also considers each imported INLINABLE overloaded function, and specialises it for the different types at which it is called in M.
and
Moreover, given a SPECIALIZE pragma for a function f, GHC will automatically create specialisations for any type-class-overloaded functions called by f, if they are in the same module as the SPECIALIZE pragma, or if they are INLINABLE; and so on, transitively.
So GHC should automatically specialize some/most/all(?) functions marked
INLINABLE
without a pragma, and if I use an explicit pragma, the specialization is transitive. My question is: is the auto-specialization transitive?Specifically, here's a small example:
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.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 specializes the call to
plus
, but does not specialize(+)
in theQux
Num
instance which kills performance.However, an explicit pragma
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
results in transitive specialization as the docs indicate, so
(+)
is specialized and the code is 30x faster (both compiled with-O2
). Is this expected behavior? Should I only expect(+)
to be specialized transitively with an explicit pragma?
UPDATE
The docs for 7.8.2 haven't changed, and the behavior is the same, so this question is still relevant.
解决方案Short answers:
The question's key points, as I understand them, are the following:
- "is the auto-specialization transitive?"
- Should I only expect (+) to be specialized transitively with an explicit pragma?
- (apparently intended) Is this a bug of GHC? Is it inconsistent with the documentation?
AFAIK, the answers are No, mostly yes but there are other means, and No.
Code inlining and type application specialization is a trade-off between speed (execution time) and code size. The default level gets some speedup without bloating the code. Choosing a more exhaustive level is left to the programmer's discretion via
SPECIALISE
pragma.Explanation:
The optimiser also considers each imported INLINABLE overloaded function, and specialises it for the different types at which it is called in M.
Suppose
f
is a function whose type includes a type variablea
constrained by a type classC a
. GHC by default specializesf
with respect to a type application (substitutinga
fort
) iff
is called with that type application in the source code of (a) any function in the same module, or (b) iff
is markedINLINABLE
, then any other module that importsf
fromB
. Thus, auto-specialization is not transitive, it only touchesINLINABLE
functions imported and called for in the source code ofA
.In your example, if you rewrite the instance of
Num
as follows:instance (Num r, Unbox r) => Num (Qux r) where (+) = quxAdd quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
quxAdd
is not specifically imported byMain
.Main
imports the instance dictionary ofNum (Qux Int)
, and this dictionary containsquxAdd
in the record for(+)
. However, although the dictionary is imported, the contents used in the dictionary are not.plus
does not callquxAdd
, it uses the function stored for the(+)
record in the instance dictionary ofNum t
. This dictionary is set at the call site (inMain
) by the compiler.这篇关于GHC自动专业化的传递性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!