GHC自动专业化的传递性 [英] Transitivity of Auto-Specialization in GHC

查看:101
本文介绍了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 the Qux 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 variable a constrained by a type class C a. GHC by default specializes f with respect to a type application (substituting a for t) if f is called with that type application in the source code of (a) any function in the same module, or (b) if f is marked INLINABLE, then any other module that imports f from B. Thus, auto-specialization is not transitive, it only touches INLINABLE functions imported and called for in the source code of A.

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 by Main. Main imports the instance dictionary of Num (Qux Int), and this dictionary contains quxAdd in the record for (+). However, although the dictionary is imported, the contents used in the dictionary are not.
  • plus does not call quxAdd, it uses the function stored for the (+) record in the instance dictionary of Num t. This dictionary is set at the call site (in Main) by the compiler.

这篇关于GHC自动专业化的传递性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆