简体   繁体   English

是否有可能使GHC优化(砍伐森林)通用功能,如catamorphisms?

[英]Is it possible to make GHC optimize (deforest) generic functions such as catamorphisms?

I really like the idea of working with catamorphisms/anamorphisms in a generic way, but it seems to me it has a significant performance drawback: 我非常喜欢以通用的方式使用catamorphisms / anamorphisms的想法,但在我看来它有一个显着的性能缺点:

Suppose we want to work with a tree structure in the categorical way - to describe different folding using a generic catamorphism function : 假设我们希望以分类方式使用树结构 - 使用通用的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

Now we can write functions like: 现在我们可以编写如下函数:

depth1 :: Tree -> Int
depth1 = catam g
  where
    g Leaf       = 0
    g (Tree l r) = max l r

Unfortunately, this approach has a significant drawback: During the computation, new instances of TreeT Int are created at every level in fmap just to be immediately consumed by g . 不幸的是,这种方法有一个明显的缺点:在计算过程中,在fmap每个级别创建TreeT Int新实例,只是为了立即被g Compared to the classical definition 与经典定义相比

depth2 :: Tree -> Int
depth2 (Fix Leaf) = 0
depth2 (Fix (Tree l r)) = max (depth1 l) (depth1 r)

our depth1 will be always slower making unnecessary strain on the GC. 我们的depth1将总是较慢,从而对GC造成不必要的压力。 One solution would be to use hylomorphisms and combine creation and folding trees together. 一种解决方案是使用hylomorphisms并将创建和折叠树组合在一起。 But often we don't want to do that, we may want a tree to be created on one place and then passed somewhere else to be folded later. 但是我们通常不希望这样做,我们可能希望在一个地方创建一棵树,然后通过其他地方再次折叠。 Or, to be folder several times with different catamorphisms. 或者,使用不同的catamorphisms多次成为文件夹。

Is there a way to make GHC optimize depth1 ? 有没有办法让GHC优化depth1 Something like inlining catam g and then fusing/deforesting g . fmap ... catam g然后融合/砍伐森林 g . fmap ... g . fmap ... inside? g . fmap ...里面?

I believe I found an answer. 我相信我找到了答案。 I remembered reading Why does GHC make fix so confounding? 我记得为什么GHC让修复如此混乱? and that suggested me a solution. 这提示我一个解决方案。

The problem with the former definition of catam is that it is recursive, and so any attempt to INLINE it is ignored. catam的前一个定义的问题在于它是递归的,因此任何INLINE过程的尝试都会被忽略。 Compiling the original version with -ddump-simpl -ddump-to-file and reading the core : 使用-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 }

is clearly worse (constructor creation/elimination in catam_$scatam , more function calls) compared to 与...相比,显然更糟糕(构造函数创建/消除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 }

But if we define catam as 但是如果我们将catam定义为

{-# INLINE catam #-}
catam :: (Functor f) => (f a -> a) -> (Fix f -> a)
catam f = let u = f . fmap u . unfix
          in u

then it is no longer recursive, only u inside is. 然后它不再是递归的,只有u在里面。 This way GHC inlines catam in the definition of depth1 and fuses fmap with depth1 's g - just what we want: 这样GHC在catam的定义中描述了depth1并将fmapdepth1g融合 - 正是我们想要的:

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 }

which is now just the same as the dump of depth2 . 现在与depth2的转储相同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM