我在让 GHC 专门化带有类约束的函数时遇到了问题。这里有一个关于我的问题的最小例子: Foohs和 美因斯。这两个文件编译(GHC7.6.2,ghc -O3 Main)并运行。
注意:
Foo.hs真的被精简了。如果您想知道为什么需要这个约束,您可以看到更多的代码 给你。如果我将代码放在一个文件中,或者进行许多其他小的更改,GHC 只是将调用内联到 plusFastCyc。这种情况在实际代码中不会发生,因为 plusFastCyc太大,GHC 无法内联,即使标记为 INLINE也是如此。关键是对 专业化的调用是对 plusFastCyc的调用,而不是内联它。在实际代码中,plusFastCyc在许多地方被调用,因此即使我可以强制 GHC 这样做,复制如此大的函数也是不可取的。
感兴趣的代码是 Foo.hs中的 plusFastCyc,转载如下:
{-# INLINEABLE plusFastCyc #-}
{-# SPECIALIZE plusFastCyc ::
forall m . (Factored m Int) =>
(FastCyc (VT U.Vector m) Int) ->
(FastCyc (VT U.Vector m) Int) ->
(FastCyc (VT U.Vector m) Int) #-}
-- Although the next specialization makes `fcTest` fast,
-- it isn't useful to me in my real program because the phantom type M is reified
-- {-# SPECIALIZE plusFastCyc ::
-- FastCyc (VT U.Vector M) Int ->
-- FastCyc (VT U.Vector M) Int ->
-- FastCyc (VT U.Vector M) Int #-}
plusFastCyc :: (Num (t r)) => (FastCyc t r) -> (FastCyc t r) -> (FastCyc t r)
plusFastCyc (PowBasis v1) (PowBasis v2) = PowBasis $ v1 + v2
Main.hs文件有两个驱动程序: vtTest,运行时间约为3秒; fcTest,使用 forall专门化的 -O3编译时,运行时间约为83秒。
对于 vtTest测试,加法代码专门用于 Unboxed向量通过 Int向量等,而通用向量代码用于 fcTest。
在第10行,您可以看到,与第167行的通用版本相比,GHC 确实编写了 plusFastCyc的专用版本。
专门化的规则在第225行。我认为这条规则应该在270行开火。(main6调用 iterate main8 y,因此 main8是 plusFastCyc应该专门化的地方。)
我的目标是通过专门化 plusFastCyc使 fcTest变得和 vtTest一样快。我发现了两种方法:
fcTest中从 GHC.Exts显式调用 inline。plusFastCyc上的 Factored m Int约束。选项1是不能令人满意的,因为在实际的代码库中,plusFastCyc是一个经常使用的操作,而且是一个 非常大函数,所以它不应该在每次使用时都内联。相反,GHC 应该调用 plusFastCyc的专用版本。选项2实际上不是一个选项,因为我需要在实际代码中的约束。
我已经尝试了各种使用(而不是使用) INLINE、 INLINABLE和 SPECIALIZE的选项,但似乎没有一个是有效的。(INLINABLE2: 我可能已经删除了太多的 plusFastCyc,使我的示例变小,所以 INLINE可能会导致函数内联。这在我的实际代码中不会发生,因为 plusFastCyc太大了。)在这个特殊的示例中,我没有得到任何 INLINABLE3或 INLINABLE4(和 INLINABLE5)警告,尽管在最小化示例之前我得到了许多 match_co警告。据推测,“问题”是规则中的 Factored m Int约束; 如果我对该约束进行更改,那么 INLINABLE0的运行速度与 INLINABLE1一样快。
我是不是做了 GHC 不喜欢的事情? 为什么 GHC 不专门设计 plusFastCyc,我怎么才能做到呢?
更新
这个问题在 GHC7.8.2中仍然存在,因此这个问题仍然是相关的。