【问题标题】:Chaining Haskell Functions in data types在数据类型中链接 Haskell 函数
【发布时间】:2013-01-06 07:02:23
【问题描述】:

假设我有以下内容:

data FuncAndValue v res = FuncAndValue (v -> res) v

chain :: (res -> new_res) -> FuncAndValue v res -> FuncAndValue v new_res
chain new_f (FuncAndValue old_f v) = FuncAndValue (new_f . old_f) v  

GHC 是否有可能通过内联将函数 new_fold_f 组合成一个函数?

基本上,在数据类型中存储函数是否会抑制优化。

我希望 GHC 能够轻松地将函数链组合成一个(即,我的结构上的“总和”不涉及重复调用代表 (+) 的 thunk,而只是内联 @987654327 @ 所以它像 for 循环一样运行。我希望将函数存储在数据类型中,然后稍后访问它们不会阻止这一点。

【问题讨论】:

  • 是的,假设 new_fold_f 的实现可用于内联。 IE。在这种情况下,它不会因为它们是参数,但是如果您在两个函数已知且内联安全的上下文中使用 chain,则链将被内联,从而内联和优化组合。我确信 GHC 核心专家能够证明这一点(或揭穿我)
  • 编写您的程序并在分析和发现实际瓶颈后担心优化。你的结构的总和比内联更像是一个严格的问题,fyi。
  • @CatPlusPlus:我正在编写一个库,而不是一个程序,所以我认为用它运行的每个程序来测试它是不切实际的。所以我想要一些通用的指导方针。
  • 您拥有的是Store comonad。 (另见What is the Store comonad?)。而你的chain 函数就是它的Functor 实例。

标签: haskell ghc


【解决方案1】:

GHC 是否有可能通过内联将函数 new_fold_f 组合成一个函数?

是的,如果它可以在没有干预 FuncAndValue 的情况下做同样的事情。当然,函数的展开需要可用,否则无论如何都不会有内联的机会。但如果有机会,将函数包装在 FuncAndValue 中几乎没有什么区别。

但是让我们问问 GHC 本身。首先是类型和一个非常简单的chaining:

module FuncAndValue where

data FuncAndValue v res = FuncAndValue (v -> res) v

infixr 7 `chain`

chain :: (res -> new_res) -> FuncAndValue v res -> FuncAndValue v new_res
chain new_f (FuncAndValue old_f v) = FuncAndValue (new_f . old_f) v

apply :: FuncAndValue v res -> res
apply (FuncAndValue f x) = f x

trivia :: FuncAndValue Int (Int,Int)
trivia = FuncAndValue (\x -> (2*x - 1, 3*x + 2)) 1

composed :: FuncAndValue Int Int
composed = chain (uncurry (+)) trivia

以及我们为triviacomposed 获得的核心(有趣的部分):

FuncAndValue.trivia1 =
  \ (x_af2 :: GHC.Types.Int) ->
    (case x_af2 of _ { GHC.Types.I# y_agp ->
     GHC.Types.I# (GHC.Prim.-# (GHC.Prim.*# 2 y_agp) 1)
     },
     case x_af2 of _ { GHC.Types.I# y_agp ->
     GHC.Types.I# (GHC.Prim.+# (GHC.Prim.*# 3 y_agp) 2)
     })

FuncAndValue.composed2 =
  \ (x_agg :: GHC.Types.Int) ->
    case x_agg of _ { GHC.Types.I# y_agp ->
    GHC.Types.I#
      (GHC.Prim.+#
         (GHC.Prim.-# (GHC.Prim.*# 2 y_agp) 1)
         (GHC.Prim.+# (GHC.Prim.*# 3 y_agp) 2))
    }

内联足够公平,没有(.) 可见。来自trivia 的两个cases 已加入,因此我们在composed 中只有一个。除非有人教 GHC 足够的代数来将 \x -> (2*x-1) + (3*x+2) 简化为 \x -> 5*x + 1,否则这与您希望的一样好。 apply composed 在编译时减少为 6,即使在单独的模块中也是如此。

但这非常简单,让我们给它一个更难破解的坚果。

until 的可内联版本(until 的当前定义是递归的,因此 GHC 不会内联它),

module WWUntil where

wwUntil :: (a -> Bool) -> (a -> a) -> a -> a
wwUntil p f = recur
  where
    recur x
        | p x       = x
        | otherwise = recur (f x)

另一个简单的功能它自己的模块,

collatzStep :: Int -> Int
collatzStep n
    | n .&. 1 == 0  = n `unsafeShiftR` 1
    | otherwise     = 3*n + 1

最后是坚果

module Hailstone (collatzLength, hailstone) where

import FuncAndValue
import CollatzStep
import WWUntil

data P = P {-# UNPACK #-} !Int {-# UNPACK #-} !Int

fstP :: P -> Int
fstP (P x _) = x

sndP :: P -> Int
sndP (P _ y) = y

hailstone :: Int -> FuncAndValue Int Int
hailstone n = sndP `chain` wwUntil ((== 1) . fstP) (\(P n k) -> P (collatzStep n) (k+1))
                   `chain` FuncAndValue (\x -> P x 0) n

collatzLength :: Int -> Int
collatzLength = apply . hailstone

我通过使用严格对对严格度分析器有所帮助。使用香草(,),第二个组件将在每个步骤中添加 1 后被拆箱并重新装箱,我无法忍受这样的浪费;)但除此之外没有相关的区别。

核心 GHC 生成(有趣的部分):

Rec {
Hailstone.$wrecur [Occ=LoopBreaker]
  :: GHC.Prim.Int#
     -> GHC.Prim.Int# -> (# GHC.Prim.Int#, GHC.Prim.Int# #)
[GblId, Arity=2, Caf=NoCafRefs, Str=DmdType LL]
Hailstone.$wrecur =
  \ (ww_sqq :: GHC.Prim.Int#) (ww1_sqr :: GHC.Prim.Int#) ->
    case ww_sqq of wild_Xm {
      __DEFAULT ->
        case GHC.Prim.word2Int#
               (GHC.Prim.and# (GHC.Prim.int2Word# wild_Xm) (__word 1))
        of _ {
          __DEFAULT ->
            Hailstone.$wrecur
              (GHC.Prim.+# (GHC.Prim.*# 3 wild_Xm) 1) (GHC.Prim.+# ww1_sqr 1);
          0 ->
            Hailstone.$wrecur
              (GHC.Prim.uncheckedIShiftRA# wild_Xm 1) (GHC.Prim.+# ww1_sqr 1)
        };
      1 -> (# 1, ww1_sqr #)
    }
end Rec }

lvl_rsz :: GHC.Types.Int -> GHC.Types.Int
[GblId, Arity=1, Caf=NoCafRefs]
lvl_rsz =
  \ (x_iog :: GHC.Types.Int) ->
    case x_iog of _ { GHC.Types.I# tpl1_B4 ->
    case Hailstone.$wrecur tpl1_B4 0 of _ { (# _, ww2_sqH #) ->
    GHC.Types.I# ww2_sqH
    }
    }

这正是没有FuncAndValue 的情况。一切都很好地内联,一个美丽的紧密循环。

基本上,在数据类型中存储函数是否会抑制优化。

如果你将函数包裹在足够多的层下,是的。但其他值也是一样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 2021-12-31
    • 2011-02-22
    • 2013-08-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多