为什么GHC在内联时考虑LHS *的语法*

根据GHC文件:

... GHC只有在函数完全应用时才会内联,其中“完全应用”表示应用于与函数定义的LHS(语法上)显示的参数一样多的参数。

如果给出的例子是两个语义等价的定义:

comp1 :: (b -> c) -> (a -> b) -> a -> c
{-# INLINE comp1 #-}
comp1 f g = x -> f (g x)

comp2 :: (b -> c) -> (a -> b) -> a -> c
{-# INLINE comp2 #-}
comp2 f g x = f (g x)

我的问题:

  • 是否只有在INLINE pragmas存在的情况下,我们才能得到这种严格的行为(即严格的LHS句法视图,RHS内联w / out优化)?

  • 当没有给出INLINE comp2 ,GHC是否将comp2这样的函数comp2comp1

  • 如果不是,为什么? 编译器通常难以查看函数的语义,并决定在多大程度上以及在哪里部分应用和INLINE?

  • 如果GHC只是将所有函数转化为级联let... in带lambda表达式的表达式中,并且对LHS没有约束,会发生什么?


  • 如果在这个例子中, c本身是一个函数类型呢? 我不清楚你的提案在这种情况下会如何解决。

    无论如何,肯定有一些情况你不希望所有的函数的论点都“拉到前面”。 例如,你可能有这样的代码:

    foo :: [Int] -> Int -> Int -> Int
    foo list = let
      -- expensive precomputation here
      bar x y = ...
      in  x y -> bar x y
    

    您希望foo部分应用,然后为结果函数的多个应用程序共享昂贵的预计算工作。 如果您将其作为foo list xy ,则您无法分享昂贵的预计算。 (我在严肃的应用程序中遇到过这种情况。)


    这是一个很好的问题。 我阅读了格拉斯哥Haskell编译器Inliner论文的一些线索,但没有找到太多内容。

    这是一个手形的解释。 GHC实际上有时comp1comp2 - 它称之为“eta扩展”。 有关详细信息,请参阅此主题:http://www.haskell.org/pipermail/glasgow-haskell-users/2011-October/020979.html

    这个eta扩展可以巧妙地改变严格性,也存在(或者是)一个问题。 看到对文档的承诺(这似乎不在当前的文档中,所以它们要么没有被重建,要么已经被修复,不确定是哪一个):http://permalink.gmane.org/gmane .comp.lang.haskell.cvs.ghc / 57721

    在任何情况下,上述线索都有SPJ解释了为什么我们通常希望尽可能朝这个方向前进。 所以要刻意去另一个方向去改善内联看起来有点愚蠢。 正如秘密文件所讨论的那样,混淆地内联并不是最好的想法 - 使得杂注更像是一个钝头锤,这样,函数就会明白这样做是否有意义,这可能会比整体上帮助更多地受到伤害,更不用说增加代码膨胀,因为模块必须一次保持不同级别的eta移位函数。

    无论如何,作为非常重要的GHC开发者,这对我来说似乎是最有可能的。


    好吧,我猜,迟到比晚起好。

    comp1comp2不仅在语义上是等价的,而且在语法上也是如此。

    在定义的等号的LHS上写论据只是语法糖,所以这两个函数是等价的:

    id1 x = x
    id2 = x -> x
    

    编辑:我意识到我没有真正回答你的问题,所以你在这里:

  • 存在用于GHC的差时这些带注释INLINE编译指示在其核心表示的函数的展开,如GHC商店和用于该元数也可以被展开(这就是Guidance=ALWAYS_IF(arity=1,...)的一部分),所以它在实践中确实很重要。

  • 我不认为它确实如此,因为comp1comp2在脱离所有优化操作的Core之后无法区分。 因此,当GHC想要创建一个新的展开式时,它可能会这样做,以显示明显的arity(例如,主要lambdas的数量)。

  • 内衬对于不饱和结合基本上是不利的,见下文。 comp1例子实际上也是comp1 :我们希望这发生的原因不是我们关心消除函数调用。 相反,我们希望comp1专门用于fg参数,而不管我们应用专门化的具体x如何。 实际上有一个应该做这种工作的优化传递,称为构造函数专门化(更多关于下面的内容)。 INLINE在这里使用甚至是一个不合适的东西:这仍然不会像comp1 (const 5)那样专门调用一个调用,它显然应该被减少到const 5

  • 因此,只要你不使用INLINE杂注散布每一个有限的东西,这个变化不会太大。 即使如此,它是有问题的,如果带来任何好处。问题是,它只是没有意义的内联没有任何进一步的动机不饱和电话(例如,功能专业化的具体参数)等相比,它只会炸毁代码大小在有一点,所以它可能会让事情变得更慢。

  • 结束编辑


    我认为为什么不内联呼叫不绑定的一个原因是,他们大多不会带来任何新的优化机会。

    f = x y -> 1 + (x * y)
    g = x y -> (1 + x) * y
    

    内联f 16产生y -> 1 + (16*y) ,这并不比f 16简单得多。 相反,代码大小显着增加(这是内联的最大缺点)。

    现在,如果有一个像g 16那样的调用,这会产生y -> (1 + 16) * y ,这会优化为y -> 17 * y 。 但是这些机会可以通过另一个优化传递,构造函数或调用模式专业化来检测。 这里的见解是,如果我们知道x的值,则1 + x可以被简化。 由于我们用文字(例如,值)来称呼g ,因此为特定呼叫站点专门化g是有益的,例如g16 = y -> 17 *y 。 不需要内联g ,其他呼叫站点可能会共享为g16生成的代码。

    这只是内联如何不需要完成而仍然具有高效代码的一个例子。 还有很多其他优化可以与内联器相互作用来实现你想要的。 例如Eta-expansion将确保呼叫尽可能饱和:

    main = print (f 2)
    
    f = g 1 
    g x y = x + y
    

    由于f总是用1个参数调用,所以我们可以扩展它:

    f eta = g 1 eta
    

    现在,对g的调用已饱和并可以内联。 Dito为f ,所以最终这减少到

    main = print 3
    
    f eta = 1 + eta
    g x y = x + y
    

    模死码消除。

    链接地址: http://www.djcxy.com/p/43185.html

    上一篇: Why does GHC consider the LHS *syntactically* when inlining?

    下一篇: How is this fibonacci