ChatGPT解决这个技术问题 Extra ChatGPT

GHC中自特化的传递性

从 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 的文档没有改变,行为也是一样的,所以这个问题仍然是相关的。

我不知道答案,但听起来可能与:ghc.haskell.org/trac/ghc/ticket/5928 如果您认为可能与 5928 相关,可能值得开一张新票或在此处添加您的信息
@jberryman 那张票和我的问题似乎有两个区别:1)在票中,plus 的等价物被 not 标记为 INLINABLE 和 2)simonpj 表示有一些内联使用票证代码,但我示例的核心显示没有内联函数(特别是,我无法摆脱第二个 Foo 构造函数,否则 GHC 内联的东西)。
啊好吧。当您定义 plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2 以使 LHS 在调用站点完全应用时会发生什么?它是否被内联,然后专业化开始了?
@jberryman 有趣的是你应该问。我一直在与 this question 一起走这条路,这导致了这个 trac report。由于这些链接,我最初完全应用了对 plus 的调用,但实际上我得到了 less 特化:对 plus 的调用也不是特化的。我对此没有任何解释,但打算把它留给另一个问题,或者希望它会在这个问题的答案中得到解决。
来自 ghc.haskell.org/trac/ghc/wiki/ReportABug:“如有疑问,请报告您的错误。”你不应该感到难过,特别是因为这里有足够数量的真正有经验的haskellers不知道如何回答你的问题。像这样的测试用例对于 GHC 开发人员来说可能真的很有价值。总之祝你好运!如果您提交票证,则更新问题

D
Diego E. Alonso-Blas

简短的答案:

据我了解,这个问题的关键点如下:

“自动专业化是可传递的吗?”我是否应该只期望 (+) 使用显式的编译指示进行传递性专门化? (显然是有意的)这是 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 中)设置。