gpt4 book ai didi

performance - Pointfree 版本使性能恶化

转载 作者:行者123 更新时间:2023-12-04 10:19:23 27 4
gpt4 key购买 nike

好吧,事实证明我在我的程序代码中定义了这个函数:

st_zipOp :: (a -> a -> a) -> Stream a -> Stream a -> Stream a
st_zipOp f xs ys = St.foldr (\x r -> st_map (f x) r) xs ys

它做它看起来做的事情。它压缩(多次应用运算符,是的)两个 Stream a 类型的元素,它是一个类似列表的类型,使用类型为 a 的内部运算符.定义非常简单。

一旦我以这种方式定义了函数,我就尝试了另一个版本:
st_zipOp :: (a -> a -> a) -> Stream a -> Stream a -> Stream a
st_zipOp = St.foldr . (st_map .)

据我所知,这与上面的定义完全相同。它只是先前定义的无点版本。

然而,我想检查是否有任何性能变化,我发现,确实,无点版本使程序运行稍差(无论是在内存上还是在时间上)。

为什么会这样?如果有必要,我可以编写一个重现此行为的测试程序。

我正在编译 -O2如果这有所作为。

简单的测试用例

我编写了以下代码,试图重现上述行为。这次我使用了列表,性能的变化不太明显,但仍然存在。这是代码:
opEvery :: (a -> a -> a) -> [a] -> [a] -> [a]
opEvery f xs ys = foldr (\x r -> map (f x) r) xs ys

opEvery' :: (a -> a -> a) -> [a] -> [a] -> [a]
opEvery' = foldr . (map .)

main :: IO ()
main = print $ sum $ opEvery (+) [1..n] [1..n]
where
n :: Integer
n = 5000

使用 opEvery 的分析结果(显式参数版本):
total time  =        2.91 secs   (2906 ticks @ 1000 us, 1 processor)
total alloc = 1,300,813,124 bytes (excludes profiling overheads)

使用 opEvery' 的分析结果(无积分版):
total time  =        3.24 secs   (3242 ticks @ 1000 us, 1 processor)
total alloc = 1,300,933,160 bytes (excludes profiling overheads)

但是,我希望这两个版本是等效的(在所有意义上)。

最佳答案

对于简单的测试用例,两个版本在使用优化编译时产生相同的内核,但没有进行分析。

在启用分析 (-prof -fprof-auto) 的情况下编译时,pointfull 版本被内联,导致主要部分是

Rec {
Main.main_go [Occ=LoopBreaker]
:: [GHC.Integer.Type.Integer] -> [GHC.Integer.Type.Integer]
[GblId, Arity=1, Str=DmdType S]
Main.main_go =
\ (ds_asR :: [GHC.Integer.Type.Integer]) ->
case ds_asR of _ {
[] -> xs_r1L8;
: y_asW ys_asX ->
let {
r_aeN [Dmd=Just S] :: [GHC.Integer.Type.Integer]
[LclId, Str=DmdType]
r_aeN = Main.main_go ys_asX } in
scctick<opEvery.\>
GHC.Base.map
@ GHC.Integer.Type.Integer
@ GHC.Integer.Type.Integer
(GHC.Integer.Type.plusInteger y_asW)
r_aeN
}
end Rec }

(没有分析你会得到更好的东西)。

在启用分析的情况下编译无点版本时, opEvery'没有内联,你得到
Main.opEvery'
:: forall a_aeW.
(a_aeW -> a_aeW -> a_aeW) -> [a_aeW] -> [a_aeW] -> [a_aeW]
[GblId,
Str=DmdType,
Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=0, Value=False,
ConLike=False, WorkFree=False, Expandable=False,
Guidance=IF_ARGS [] 80 60}]
Main.opEvery' =
\ (@ a_c) ->
tick<opEvery'>
\ (x_ass :: a_c -> a_c -> a_c) ->
scc<opEvery'>
GHC.Base.foldr
@ a_c
@ [a_c]
(\ (x1_XsN :: a_c) -> GHC.Base.map @ a_c @ a_c (x_ass x1_XsN))

Main.main4 :: [GHC.Integer.Type.Integer]
[GblId,
Str=DmdType,
Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=0, Value=False,
ConLike=False, WorkFree=False, Expandable=False,
Guidance=IF_ARGS [] 40 0}]
Main.main4 =
scc<main>
Main.opEvery'
@ GHC.Integer.Type.Integer
GHC.Integer.Type.plusInteger
Main.main7
Main.main5

当您添加 {-# INLINABLE opEvery' #-} pragma,即使在编译分析时也可以内联,给出
Rec {
Main.main_go [Occ=LoopBreaker]
:: [GHC.Integer.Type.Integer] -> [GHC.Integer.Type.Integer]
[GblId, Arity=1, Str=DmdType S]
Main.main_go =
\ (ds_asz :: [GHC.Integer.Type.Integer]) ->
case ds_asz of _ {
[] -> lvl_r1KU;
: y_asE ys_asF ->
GHC.Base.map
@ GHC.Integer.Type.Integer
@ GHC.Integer.Type.Integer
(GHC.Integer.Type.plusInteger y_asE)
(Main.main_go ys_asF)
}
end Rec }

这甚至比 pragma-less pointfull 版本快一点,因为它不需要勾选计数器。
Stream 很可能发生了类似的效果。案子。

外卖:
  • 分析会抑制优化。没有分析的等效代码可能没有分析支持。
  • 切勿使用为分析或未优化而编译的代码来衡量性能。
  • 分析可以帮助您找出代码中的时间花在哪里[但是,有时启用分析可以完全改变代码的行为;任何严重依赖重写规则优化和/或内联的东西都是发生这种情况的候选者],但它不能告诉你你的代码有多快。
  • 关于performance - Pointfree 版本使性能恶化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14610081/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com