【发布时间】:2013-01-30 17:30:06
【问题描述】:
好吧,原来我在我的程序代码中定义了这个函数:
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
它做它看起来做的事情。它使用a 类型的内部运算符压缩(多次应用运算符,是的)Stream 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)
但是,我希望这两个版本是等效的(在所有意义上)。
【问题讨论】:
-
是的,如果您发布完整的可运行代码来演示该问题,它可能会有所帮助。编译器版本、代码使用模式等之间存在如此多的差异,仅通过模糊的描述很难提供帮助。
-
想到的一件事是,由于应用程序已经饱和,GHC 可能更容易内联第一个版本的代码。
-
并使用
ghc -O2 -prof -fprof-auto hi.hs编译? (这些细节很重要!)我在以这种方式编译和运行时看到了不同,所以这可能是一些分析怪癖。分析可能会干扰内联和重写规则等,所以我不会太在意这一点(除非有充分的理由关心分析运行时?)。 -
@DanielDíaz:不幸的是,为分析编译的 Haskell 代码并不能完全说明未分析代码的性能。分歧可能比这大得多。至少对于列表版本,差异完全是由于启用分析所施加的限制。
-
@shachaf 当然,这些标志的代码会有所不同(我确实检查过)。我的观点是,差异只是通过构建用于分析而施加的,而非分析的构建使用完全相同函数。
标签: performance haskell pointfree