Haskell平台:嵌套函数和优化

时间:2012-03-18 10:16:16

标签: optimization haskell haskell-platform

在haskell平台的许多功能的实现中有一个非常常见的模式困扰我,但我无法找到解释。它是关于使用嵌套函数进行优化的。

嵌套函数在where子句用于进行尾递归时的原因对我来说非常清楚(如在length中),但内部函数与top的类型完全相同的目的是什么-一级?例如,它发生在Data.Set module的许多函数中,如下所示:

-- | /O(log n)/. Is the element in the set?
member :: Ord a => a -> Set a -> Bool
member = go
  where
    STRICT_1_OF_2(go)
    go _ Tip = False
    go x (Bin _ y l r) = case compare x y of
          LT -> go x l
          GT -> go x r
          EQ -> True
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE member #-}
#else
{-# INLINE member #-}
#endif

我怀疑它可能与记忆有关,但我不确定。


编辑:由于dave4420建议严格,以下是可以在同一模块中找到的STRICT_1_OF_2宏的定义:

-- Use macros to define strictness of functions.
-- STRICT_x_OF_y denotes an y-ary function strict in the x-th parameter.
-- We do not use BangPatterns, because they are not in any standard and we
-- want the compilers to be compiled by as many compilers as possible.
#define STRICT_1_OF_2(fn) fn arg _ | arg `seq` False = undefined

我理解这个宏是如何工作的,但是我仍然不明白为什么goSTRICT_1_OF_2(go)一起不应该移到顶层代替member

也许这不是因为优化,而是因为风格选择?


编辑2 :我在设置模块中添加了INLINABLEINLINE部分。我没想到乍一看他们可以和它有关。

2 个答案:

答案 0 :(得分:16)

在本地工作者周围拥有INLINABLE(或INLINE)包装器的一个直接好处是专业化。在调用站点上使用固定元素类型定义member的方式,Ord字典可以被丢弃,并且相应的compare函数内联或至少作为静态参数传递

使用直接递归定义,member成为一个循环断路器并且没有内联,因此呼叫站点专业化没有完成 - 至少是INLINABLE之前的故事附注

使用INLINABLE编译指示,调用站点专业化确实发生,字典被删除,​​但我有几次尝试没有设法编写一个与当前一样高效的直接递归member 。但是对于INLINE pragma,法律仍然存在,没有内联的断路器;对于member,这意味着没有可以消除字典的呼叫站点专业化。

因此,可能没有必要再以该样式编写函数,但是,获得调用站点的专业化。

答案 1 :(得分:13)

GHC无法内联递归函数。定义member的方式,递归仅限于go,而member本身不是递归的,可以内联。

来自GHC user guide:

  

GHC确保内联不能永远持续下去:每一个   相互递归组由一个或多个环路断路器切断   从未内联(参见GHC内联机构的秘密,JFP 12(4)2002年7月)。   GHC尝试不选择具有INLINE编译指示的函数作为循环   断路器,但是当没有选择时,即使是INLINE功能也可以   选中,在这种情况下,INLINE编译指示将被忽略。例如,对于   一个自递归函数,环路断路器只能是函数   本身,所以INLINE编译指示总是被忽略。