具有约束的专门化

我在让 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,因此 main8plusFastCyc应该专门化的地方。)

我的目标是通过专门化 plusFastCyc使 fcTest变得和 vtTest一样快。我发现了两种方法:

  1. fcTest中从 GHC.Exts显式调用 inline
  2. 删除 plusFastCyc上的 Factored m Int约束。

选项1是不能令人满意的,因为在实际的代码库中,plusFastCyc是一个经常使用的操作,而且是一个 非常大函数,所以它不应该在每次使用时都内联。相反,GHC 应该调用 plusFastCyc的专用版本。选项2实际上不是一个选项,因为我需要在实际代码中的约束。

我已经尝试了各种使用(而不是使用) INLINEINLINABLESPECIALIZE的选项,但似乎没有一个是有效的。(INLINABLE2: 我可能已经删除了太多的 plusFastCyc,使我的示例变小,所以 INLINE可能会导致函数内联。这在我的实际代码中不会发生,因为 plusFastCyc太大了。)在这个特殊的示例中,我没有得到任何 INLINABLE3或 INLINABLE4(和 INLINABLE5)警告,尽管在最小化示例之前我得到了许多 match_co警告。据推测,“问题”是规则中的 Factored m Int约束; 如果我对该约束进行更改,那么 INLINABLE0的运行速度与 INLINABLE1一样快。

我是不是做了 GHC 不喜欢的事情? 为什么 GHC 不专门设计 plusFastCyc,我怎么才能做到呢?

更新

这个问题在 GHC7.8.2中仍然存在,因此这个问题仍然是相关的。

6246 次浏览

GHC 还为 SPECIALIZE提供了一个类型类实例声明选项。我用 Foo.hs的(扩展的)代码尝试了这种方法,放置了以下代码:

instance (Num r, V.Vector v r, Factored m r) => Num (VT v m r) where
{-# SPECIALIZE instance ( Factored m Int => Num (VT U.Vector m Int)) #-}
VT x + VT y = VT $ V.zipWith (+) x y

然而,这种改变并没有达到预期的加速效果。实现这一性能改进的是 手动操作为具有相同函数定义的 VT U.Vector m Int类型增加了一个专门实例,如下所示:

instance (Factored m Int) => Num (VT U.Vector m Int) where
VT x + VT y = VT $ V.zipWith (+) x y

这需要在 LANGUAGE中添加 OverlappingInstancesFlexibleInstances

有趣的是,在示例程序中,即使删除了每个 SPECIALIZEINLINABLE杂注,通过重叠实例获得的加速仍然存在。