[英]Chaining Haskell Functions in data types
Lets say I have the following: 可以说我有以下内容:
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
Is GHC likely to be able to combine functions new_f
and old_f
into a single function through inlining? GHC是否可以通过内联将函数new_f
和old_f
成单个函数?
Basically, does storing functions in data types in anyway inhibit optimizations. 基本上,以数据类型存储函数无论如何都会抑制优化。
I'd like GHC to be easily able to compose chains of functions into one (ie so a "sum" on my structure doesn't involve repeated calls to a thunk that represents (+)
and instead just inlines the (+)
so it runs like a for loop. I'm hoping storing functions in data types and then accessing them later doesn't inhibit this. 我希望GHC能够轻松地将函数链组合成一个(也就是说,我的结构上的“总和”不涉及重复调用表示(+)
的thunk,而只是内联(+)
所以它像for循环一样运行。我希望将函数存储在数据类型中,然后稍后访问它们不会抑制它。
Is GHC likely to be able to combine functions
new_f
andold_f
into a single function through inlining? GHC是否可以通过内联将函数new_f
和old_f
成单个函数?
Yes, if it could do the same without the intervening FuncAndValue
. 是的,如果没有介入的FuncAndValue
可以做同样的FuncAndValue
。 Of course the unfoldings of the functions need to be available, or there wouldn't be any chance of inlining anyway. 当然,需要提供功能的展开,或者无论如何也不会有任何内联的机会。 But if there is a chance, wrapping the function(s) in a FuncAndValue
makes little difference if any. 但是如果有可能的话,将函数包装在FuncAndValue
就没什么区别了。
But let's ask GHC itself. 但是让我们问一下GHC本身。 First the type and a very simple chain
ing: 首先是类型和非常简单的chain
:
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
and (the interesting part of) the core we get for trivia
and composed
: 和(有趣的部分)我们得到的trivia
和composed
的核心:
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))
}
Inlined fair enough, no (.)
to be seen. 内联公平,没有(.)
可见。 The two case
s from trivia
have been joined so that we have only one in composed
. 来自trivia
的两个case
已经加入,因此我们只有一个composed
。 Unless somebody teaches GHC enough algebra to simplify \\x -> (2*x-1) + (3*x+2)
to \\x -> 5*x + 1
, that's as good as you can hope. 除非有人教GHC足够的代数来简化\\x -> (2*x-1) + (3*x+2)
到\\x -> 5*x + 1
,这就像你希望的那样好。 apply composed
is reduced to 6
at compile time, even in a separate module. apply composed
编译在编译时减少到6
,即使在单独的模块中也是如此。
But that was very simple, let's give it a somewhat harder nut to crack. 但这很简单,让我们给它一个更难解决的难题。
An inlinable version of until
(the current definition of until
is recursive, so GHC doesn't inline it), 一个可以内联版本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)
Another simple function it its own module, 另一个简单的功能是它自己的模块,
collatzStep :: Int -> Int
collatzStep n
| n .&. 1 == 0 = n `unsafeShiftR` 1
| otherwise = 3*n + 1
and finally, the nut 最后,坚果
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
I have helped the strictness analyser a bit by using a strict pair. 我使用严格的一对帮助了严格性分析器。 With the vanilla (,)
the second component would be unboxed and reboxed after adding 1 in each step, and I just can't bear such waste ;) But otherwise there's no relevant difference. 使用香草(,)
,第二个组件将在每个步骤中添加1后取消装箱并重新装箱,我不能忍受这样的浪费;)但是否则没有相关的区别。
And (the interesting part of) the core GHC generates: 核心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
}
}
and that's exactly what you get without FuncAndValue
. 而这正是你在没有FuncAndValue
情况下FuncAndValue
。 Everything inlined nicely, a beautiful tight loop. 一切都很好地勾勒出一个美丽的紧凑循环。
Basically, does storing functions in data types in anyway inhibit optimizations. 基本上,以数据类型存储函数无论如何都会抑制优化。
If you wrap the function under enough layers, yes. 如果将函数包装在足够的图层下,是的。 But it's the same with other values. 但它与其他价值观相同。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.