为什么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
这样的函数comp2
为comp1
?
如果不是,为什么? 编译器通常难以查看函数的语义,并决定在多大程度上以及在哪里部分应用和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实际上有时comp1
为comp2
- 它称之为“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开发者,这对我来说似乎是最有可能的。
好吧,我猜,迟到比晚起好。
comp1
和comp2
不仅在语义上是等价的,而且在语法上也是如此。
在定义的等号的LHS上写论据只是语法糖,所以这两个函数是等价的:
id1 x = x
id2 = x -> x
编辑:我意识到我没有真正回答你的问题,所以你在这里:
存在用于GHC的差时这些带注释INLINE
编译指示在其核心表示的函数的展开,如GHC商店和用于该元数也可以被展开(这就是Guidance=ALWAYS_IF(arity=1,...)
的一部分),所以它在实践中确实很重要。
我不认为它确实如此,因为comp1
和comp2
在脱离所有优化操作的Core之后无法区分。 因此,当GHC想要创建一个新的展开式时,它可能会这样做,以显示明显的arity(例如,主要lambdas的数量)。
内衬对于不饱和结合基本上是不利的,见下文。 comp1
例子实际上也是comp1
:我们希望这发生的原因不是我们关心消除函数调用。 相反,我们希望comp1
专门用于f
和g
参数,而不管我们应用专门化的具体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?