2012-03-18 71 views
19

Haskell平臺中的許多函數的實現中有一個非常常見的模式困擾我,但我無法找到解釋。這是關於使用嵌套函數進行優化。Haskell平臺:嵌套函數和優化

原因在where子句中嵌套函數時,他們的目標是使尾遞歸是很清楚,我(在length),但目的是什麼,當內部函數具有完全相同的類型作爲最高一級?它發生,例如,在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 

我瞭解這個宏的作品,但是我仍然不明白爲什麼go連同STRICT_1_OF_2(go)不應該被移到頂層而不是member

也許這不是因爲優化,而僅僅是因爲文體選擇?


編輯2:我增加了INLINABLEINLINE部從所述一組模塊。我沒有想到,他們乍一看可能會有什麼用處。

+1

我懷疑這與嚴格分析有關:編譯器應該推斷出'member'的第一個參數必須被評估,但'go'的第一個參數總是被評估過。但我不確定。 – dave4420 2012-03-18 10:23:46

+0

@ dave4420:謝謝你的建議。我用一些關於函數嚴格性的更多信息更新了我的問題,我希望這會有所幫助。 – 2012-03-18 10:51:45

+1

我認爲這純粹是一個風格問題。但是你可以通過'go'中的'x'釋放一些微小的增益。我不喜歡這樣寫蜜蜂的風格,因爲它似乎暗示x在每次迭代中都會被seq:ed。另外,當它不一定是(但那很小)時,它在'x'中使成員嚴格。 – augustss 2012-03-18 11:24:16

回答

16

一個直接的好處就是專業化。 member在調用站點上使用固定元素類型定義的方式,Ord字典可以被丟棄,並且合適的compare函數內聯或至少作爲靜態參數傳遞。

有了直接遞歸定義,member成爲環開關和斷路器不內聯,所以叫現場的專業化不這樣做 - 這是,至少,在INLINABLE編譯之前的故事。

使用INLINABLE編譯指示,呼叫站點專門化確實發生,字典被刪除,​​但我有一些嘗試沒有設法編寫一個與當前一樣高效的直接遞歸member。但有了INLINE編譯指示,法律仍然有效,不會內聯斷路器;對於member這意味着沒有呼叫站點專業化,以消除字典是可能的。

因此,可能不再需要以該風格編寫函數,但它是爲了獲得呼叫站點專業化。

13

GHC不能內聯遞歸函數。定義方式member,遞歸限於gomember本身不遞歸併且可以內聯。

GHC user guide:

GHC保證了內聯不能永遠持續下去:每次 相互遞歸組由被 從不內聯(見GHC襯裏,JFP的祕密一個或多個迴路斷路器切12(4)2002年7月)。 GHC嘗試不選擇帶有INLINE雜注的函數作爲循環 斷路器,但是如果沒有選擇,即使可以選擇INLINE函數 ,在這種情況下,INLINE雜注將被忽略。例如,對於 自遞歸函數,環路斷路器本身只能是功能 ,所以始終忽略INLINE雜注。周圍有當地工人的INLINABLE(或INLINE)包裝的

+1

謝謝。有了這個,我更近了一步,但我仍然不明白。首先聲明它是「INLINE」,如果它只是調用另一個非內聯函數'go',那麼有什麼意義呢? – 2012-03-18 11:32:52

+0

所以,通過定義一個嵌套函數,我們以某種方式允許甚至爲遞歸函數'member'提供內聯,讓GHC有機會選擇'go'作爲循環斷路器,對吧? – 2012-03-18 12:01:25

+1

@Riccardo,好點。在http://hackage.haskell.org/trac/ghc/wiki/Inlining 有一些相關的信息也許這可以澄清一點。 – 2012-03-18 12:27:58