我在让 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
的选项,但似乎没有一个是有效的。(INLINABLE
2: 我可能已经删除了太多的 plusFastCyc
,使我的示例变小,所以 INLINE
可能会导致函数内联。这在我的实际代码中不会发生,因为 plusFastCyc
太大了。)在这个特殊的示例中,我没有得到任何 INLINABLE
3或 INLINABLE
4(和 INLINABLE
5)警告,尽管在最小化示例之前我得到了许多 match_co
警告。据推测,“问题”是规则中的 Factored m Int
约束; 如果我对该约束进行更改,那么 INLINABLE
0的运行速度与 INLINABLE
1一样快。
我是不是做了 GHC 不喜欢的事情? 为什么 GHC 不专门设计 plusFastCyc
,我怎么才能做到呢?
更新
这个问题在 GHC7.8.2中仍然存在,因此这个问题仍然是相关的。