在Haskell中避免分配
我正在做一个更复杂的程序,我希望能非常高效,但我现在已经把我的担忧归结为以下简单的程序:在Haskell中避免分配,haskell,optimization,Haskell,Optimization,我正在做一个更复杂的程序,我希望能非常高效,但我现在已经把我的担忧归结为以下简单的程序: main :: IO () main = print $ foldl (+) 0 [(1::Int)..1000000] 我在这里构建并运行它 $ uname -s -r -v -m Linux 3.12.9-x86_64-linode37 #1 SMP Mon Feb 3 10:01:02 EST 2014 x86_64 $ ghc -V The Glorious Glasgow Haskell Com
main :: IO ()
main = print $ foldl (+) 0 [(1::Int)..1000000]
我在这里构建并运行它
$ uname -s -r -v -m
Linux 3.12.9-x86_64-linode37 #1 SMP Mon Feb 3 10:01:02 EST 2014 x86_64
$ ghc -V
The Glorious Glasgow Haskell Compilation System, version 7.4.1
$ ghc -O -prof --make B.hs
$ ./B +RTS -P
500000500000
$ less B.prof
Sun Feb 16 16:37 2014 Time and Allocation Profiling Report (Final)
B +RTS -P -RTS
total time = 0.04 secs (38 ticks @ 1000 us, 1 processor)
total alloc = 80,049,792 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc ticks bytes
CAF Main 100.0 99.9 38 80000528
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc ticks bytes
MAIN MAIN 44 0 0.0 0.0 100.0 100.0 0 10872
CAF Main 87 0 100.0 99.9 100.0 99.9 38 80000528
CAF GHC.IO.Handle.FD 85 0 0.0 0.0 0.0 0.0 0 34672
CAF GHC.Conc.Signal 83 0 0.0 0.0 0.0 0.0 0 672
CAF GHC.IO.Encoding 76 0 0.0 0.0 0.0 0.0 0 2800
CAF GHC.IO.Encoding.Iconv 60 0 0.0 0.0 0.0 0.0 0 248
看起来每个迭代要分配80个字节。我认为期望编译器在这里生成无分配代码是很合理的
我的期望不合理吗?分配是否是启用分析的副作用?我如何才能找到摆脱分配的方法呢?在这种情况下,GHC似乎足够聪明,可以将
foldl
优化为更严格的形式,但GHC无法优化掉中间列表,因为foldl
不是一个,所以这些分配大概是针对(:)
构造函数的。(EDIT3:不,看起来不是这样;请参阅评论)
通过使用foldr
fusion,您可以摆脱中间列表:
main :: IO ()
main = print $ foldr (+) 0 [(1::Int)..1000000]
…如您所见:
k +RTS -P -RTS
total time = 0.01 secs (10 ticks @ 1000 us, 1 processor)
total alloc = 45,144 bytes (excludes profiling overheads)
它与我的内存配置文件相同
main = print $ (1784293664 :: Int)
编辑:在这个新版本中,我们用堆分配换取堆栈上的一堆(1+(2+(3+…))
。要真正获得一个好的循环,我们必须手工编写,如:
main = print $ add 1000000
add :: Int -> Int
add nMax = go 0 1 where
go !acc !n
| n == nMax = acc + n
| otherwise = go (acc+n) (n+1)
显示:
total time = 0.00 secs (0 ticks @ 1000 us, 1 processor)
total alloc = 45,144 bytes (excludes profiling overheads)
EDIT2我还没有开始使用Gabriel Gonzalez,但它也可能值得您在应用程序中使用。使用。在SO和Haskell wiki上,对不同地方的差异进行了很好的讨论。@DanielWagner我用
foldl'
@NovaDenizen得到了完全相同的结果,我希望你用foldl'
得到同样的结果,因为分析的副作用。您可以尝试正常编译,然后使用+RTS-s
运行,这将以最小的干扰方式生成非常有用的信息。无论如何对我有用。你在改变你的尺寸吗?我得到堆栈空间溢出:当前大小为8388608字节。
现在。我可以使用/B+RTS-K24M-P
来解决这个问题,但是在堆栈上分配24兆对我来说并没有太大的好处。@NovaDenizen它对我来说并没有溢出(在GHC 7.6上;也许默认值更高),但你绝对是对的,我们用堆分配来换取堆栈上的一堆(1+(2+(3+…)
。对不起,我的帖子在这一点上有误导性;我更新了答案,在GHC7系列中,它变成了自动增长的堆栈,这就是为什么它不再溢出的原因;它看起来像7.8,但不是7.6具有动态增长的堆栈内容。整洁的谢谢@CarlIt,不用手工编写循环,只需使用foldl'
foldl'
在列表融合的意义上不是一个“好消费者”,但在尾部递归的意义上却是一个好消费者。在这种情况下,没有中间列表。