![](/img/trans.png)
[英]Will GHC optimize a*a*a*a*a*a to (a*a*a)*(a*a*a)?
[英]Is it possible to make GHC optimize (deforest) generic functions such as catamorphisms?
我非常喜歡以通用的方式使用catamorphisms / anamorphisms的想法,但在我看來它有一個顯着的性能缺點:
假設我們希望以分類方式使用樹結構 - 使用通用的catamorphism函數描述不同的折疊:
newtype Fix f = Fix { unfix :: f (Fix f) }
data TreeT r = Leaf | Tree r r
instance Functor TreeT where
fmap f Leaf = Leaf
fmap f (Tree l r) = Tree (f l) (f r)
type Tree = Fix TreeT
catam :: (Functor f) => (f a -> a) -> (Fix f -> a)
catam f = f . fmap (catam f) . unfix
現在我們可以編寫如下函數:
depth1 :: Tree -> Int
depth1 = catam g
where
g Leaf = 0
g (Tree l r) = max l r
不幸的是,這種方法有一個明顯的缺點:在計算過程中,在fmap
每個級別創建TreeT Int
新實例,只是為了立即被g
。 與經典定義相比
depth2 :: Tree -> Int
depth2 (Fix Leaf) = 0
depth2 (Fix (Tree l r)) = max (depth1 l) (depth1 r)
我們的depth1
將總是較慢,從而對GC造成不必要的壓力。 一種解決方案是使用hylomorphisms並將創建和折疊樹組合在一起。 但是我們通常不希望這樣做,我們可能希望在一個地方創建一棵樹,然后通過其他地方再次折疊。 或者,使用不同的catamorphisms多次成為文件夾。
有沒有辦法讓GHC優化depth1
? 像catam g
然后融合/砍伐森林 g . fmap ...
g . fmap ...
里面?
我相信我找到了答案。 我記得為什么GHC讓修復如此混亂? 這提示我一個解決方案。
catam
的前一個定義的問題在於它是遞歸的,因此任何INLINE過程的嘗試都會被忽略。 使用-ddump-simpl -ddump-to-file
編譯原始版本並讀取核心 :
Main.depth1 = Main.catam_$scatam @ GHC.Types.Int Main.depth3
Main.depth3 =
\ (ds_dyI :: Main.TreeT GHC.Types.Int) ->
case ds_dyI of _ {
Main.Leaf -> Main.depth4;
Main.Tree l_aah r_aai -> GHC.Classes.$fOrdInt_$cmax l_aah r_aai
}
Main.depth4 = GHC.Types.I# 0
Rec {
Main.catam_$scatam =
\ (@ a_ajB)
(eta_B1 :: Main.TreeT a_ajB -> a_ajB)
(eta1_X2 :: Main.Fix Main.TreeT) ->
eta_B1
(case eta1_X2
`cast` (Main.NTCo:Fix <Main.TreeT>
:: Main.Fix Main.TreeT ~# Main.TreeT (Main.Fix Main.TreeT))
of _ {
Main.Leaf -> Main.Leaf @ a_ajB;
Main.Tree l_aan r_aao ->
Main.Tree
@ a_ajB
(Main.catam_$scatam @ a_ajB eta_B1 l_aan)
(Main.catam_$scatam @ a_ajB eta_B1 r_aao)
})
end Rec }
與...相比,顯然更糟糕(構造函數創建/消除catam_$scatam
,更多函數調用)
Main.depth2 =
\ (w_s1Rz :: Main.Tree) ->
case Main.$wdepth2 w_s1Rz of ww_s1RC { __DEFAULT ->
GHC.Types.I# ww_s1RC
}
Rec {
Main.$wdepth2 [Occ=LoopBreaker] :: Main.Tree -> GHC.Prim.Int#
[GblId, Arity=1, Caf=NoCafRefs, Str=DmdType S]
Main.$wdepth2 =
\ (w_s1Rz :: Main.Tree) ->
case w_s1Rz
`cast` (Main.NTCo:Fix <Main.TreeT>
:: Main.Fix Main.TreeT ~# Main.TreeT (Main.Fix Main.TreeT))
of _ {
Main.Leaf -> 0;
Main.Tree l_aaj r_aak ->
case Main.$wdepth2 l_aaj of ww_s1RC { __DEFAULT ->
case Main.$wdepth2 r_aak of ww1_X1Sh { __DEFAULT ->
case GHC.Prim.<=# ww_s1RC ww1_X1Sh of _ {
GHC.Types.False -> ww_s1RC;
GHC.Types.True -> ww1_X1Sh
}
}
}
}
end Rec }
但是如果我們將catam
定義為
{-# INLINE catam #-}
catam :: (Functor f) => (f a -> a) -> (Fix f -> a)
catam f = let u = f . fmap u . unfix
in u
然后它不再是遞歸的,只有u
在里面。 這樣GHC在catam
的定義中描述了depth1
並將fmap
與depth1
的g
融合 - 正是我們想要的:
Main.depth1 =
\ (w_s1RJ :: Main.Tree) ->
case Main.$wdepth1 w_s1RJ of ww_s1RM { __DEFAULT ->
GHC.Types.I# ww_s1RM
}
Rec {
Main.$wdepth1 [Occ=LoopBreaker] :: Main.Tree -> GHC.Prim.Int#
[GblId, Arity=1, Caf=NoCafRefs, Str=DmdType S]
Main.$wdepth1 =
\ (w_s1RJ :: Main.Tree) ->
case w_s1RJ
`cast` (Main.NTCo:Fix <Main.TreeT>
:: Main.Fix Main.TreeT ~# Main.TreeT (Main.Fix Main.TreeT))
of _ {
Main.Leaf -> 0;
Main.Tree l_aar r_aas ->
case Main.$wdepth1 l_aar of ww_s1RM { __DEFAULT ->
case Main.$wdepth1 r_aas of ww1_X1So { __DEFAULT ->
case GHC.Prim.<=# ww_s1RM ww1_X1So of _ {
GHC.Types.False -> ww_s1RM;
GHC.Types.True -> ww1_X1So
}
}
}
}
end Rec }
現在與depth2
的轉儲相同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.