专业化与约束

156
我遇到了一个问题,无法让GHC对带有类约束的函数进行特化。这里有一个我的问题的最小示例:Foo.hsMain.hs。这两个文件可以编译(GHC 7.6.2,ghc -O3 Main)并运行。

注意: Foo.hs非常简化。如果您想了解为什么需要约束条件,可以在这里看到更多代码。如果我将代码放在单个文件中或进行许多其他微小的更改,GHC会直接内联调用plusFastCyc。这在真实代码中不会发生,因为plusFastCyc太大了,即使标记为INLINE,GHC也无法内联。重点是要专门化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秒,以及使用forall特化编译时需要约83秒的fcTest

core shows显示,对于vtTest测试,加法代码被专门化为Unboxed向量和Int等类型,而对于fcTest,使用通用向量代码。 在第10行,可以看到GHC确实写了一个专门化版本的plusFastCyc,与第167行的通用版本相比。 专门化规则在第225行。我认为这条规则应该在第270行触发。(main6调用iterate main8 y,因此plusFastCyc应该在main8中进行专门化。)

我的目标是通过专门化plusFastCyc,使fcTestvtTest一样快。我找到了两种方法来实现这一点:

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

选项1不令人满意,因为在实际代码库中,plusFastCyc是一个经常使用的操作和一个非常大的函数,所以它不应该在每次使用时进行内联。相反,GHC应该调用一个专门的版本的plusFastCyc。选项2并不是一个真正的选项,因为我需要在真正的代码中使用这个约束条件。

我尝试了多种选项,使用和不使用INLINEINLINABLESPECIALIZE,但似乎没有什么作用。(编辑:我可能剥夺了太多的plusFastCyc,以使我的例子更小,因此INLINE可能会导致函数内联。这在我的真实代码中并不会发生,因为plusFastCyc太大了。)在这个特定的例子中,我没有得到任何match_co: needs more casesRULE: LHS too complicated to desugar(以及here)警告,尽管在最小化示例之前我收到了很多match_co警告。可以推测,“问题”是规则中的Factored m Int约束;如果我对该约束进行更改,则fcTest运行速度与vtTest一样快。

我是否做了一些GHC不喜欢的事情?为什么GHC不会专门化plusFastCyc,我该如何做呢?

更新

问题在GHC 7.8.2中仍存在,因此这个问题仍然是相关的。


3
我刚刚尝试为一个特定的 m,也就是 M 进行专门化处理。这样做可以完成任务,但在实际程序中我无法为特定的幻象类型进行专门化处理,因为它们被实例化了。 - crockeea
我也提交了一个 GHC bug 报告 https://ghc.haskell.org/trac/ghc/ticket/8668 但问题仍然存在。报告过程帮助我整理了一下问题,所以希望能更容易地找出问题所在。 - crockeea
@monojohnny 很抱歉听到这样的事情,我相信你可以将其标记为这样。我认为我正在要求 GHC 做一些相当合理的事情,但它却不会这样做。我不确定我是在做错了什么,还是这是编译器的特异性,可能有一种解决方法。我曾经在 hackage 上的某个特定库中看到过针对专业化和规则的解决方法,但我希望社区中比我更有 GHC 经验的人知道如何实现专业化。 - crockeea
1
我为我的评论语气道歉 - 这不是我对这个网站最好的贡献 - 你的帖子真的没有任何问题(我想是我理解不够引起了我的烦恼!) - monojohnny
@monojohnny,道歉已经被接受了,但很遗憾现在无法取消那个踩的操作了;-) - crockeea
啊,是的,对不起 - 我相信你可以编辑帖子,这将允许我中和负评。 - monojohnny
1个回答

5

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 pragma,使用重叠实例获得的加速仍然存在。

肯定不是最优解,但这是第一个真正实现目标的解决方案,所以我想我现在就接受它吧... - crockeea

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接