GHC中自动专业化的传递性
从GHC 7.6 文档 :
[Y]你通常甚至不需要SPECIALIZE附注。 当编译一个模块M时,GHC的优化器(带有-O)会自动考虑在M中声明的每个顶层重载函数,并且专门针对它在M中调用的不同types。优化器还考虑每个导入的INLINABLE重载函数,并专门针对它在M中被称为的不同types
和
此外,给定一个函数f的SPECIALIZE pragma,GHC将自动为f调用的任何类重载函数创build专门化,如果它们与SPECIALIZE pragma在同一个模块中,或者它们是INLINABLE; 等等。
因此,GHC应该自动地专门化一些标记为INLINABLE
一些/ most / all(?)函数而没有附注,如果我使用一个明确的附注,专门化是传递的。 我的问题是: 汽车专业化传递?
具体来说,这是一个小例子:
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 (+) xy {-# INLINABLE plus #-} plus :: (Num t) => (Foo t) -> (Foo t) -> (Foo t) plus (Bar v1) (Bar v2) = Bar $ v1 + v2
GHC专门调用plus
,但并不专门(+)
Qux
(+)
在Qux
Num
实例中杀死性能。
但是,一个明确的杂注
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
导致文档指示的传递性专门化 ,所以(+)
是专用的,代码速度提高了30倍(均使用-O2
编译)。 这是预期的行为? 我是否应该只希望(+)
用一个明确的附注来传递专门知识?
UPDATE
7.8.2的文档没有改变,行为也是一样的,所以这个问题还是相关的。
简短的答案:
根据我的理解,这个问题的关键点如下:
- “是自动专业化的传递?
- 我是否应该只希望(+)用一个明确的附注来传递专门知识?
- (显然打算)这是GHC的错误? 是否与文档不一致?
AFAIK,答案是否定的,大部分是的,但还有其他的方法,并没有。
代码内联和types应用程序专门化是速度(执行时间)和代码大小之间的折衷。 默认级别得到一些加速而不会膨胀的代码。 select一个更详尽的级别是由程序员通过SPECIALISE
注决定的。
说明:
优化器还考虑每个导入的INLINABLE重载函数,并将其专门用于在M中调用它的不同types。
假设f
是一个函数,其types包含一个由types类C a
约束的typesvariablesC a
。 如果在同一个模块中的任何函数的源代码中使用该types的应用程序调用f
,或者(b)如果f
被标记为INLINABLE
,那么GHC缺省地将types应用程序专用于f
(代替a
)那么从B
导入 f
任何其他模块。 因此,自动分类不是传递的,它只涉及A
的源代码中导入和调用的INLINABLE
函数。
在你的例子中,如果你重写Num
的实例如下:
instance (Num r, Unbox r) => Num (Qux r) where (+) = quxAdd quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) xy
-
quxAdd
不是由Main
专门导入的。Main
导入Num (Qux Int)
的实例字典,并且该字典在(+)
的logging中包含quxAdd
。 但是,虽然字典是导入的,但字典中使用的内容不是。 -
plus
不调用quxAdd
,它使用为Num t
的实例字典中的(+)
logging存储的函数。 这个字典是由编译器在调用站点(在Main
)设置的。