我有一个简单的问题:给定一个整数列表,将第一行读为N
。然后,读取接下来的N行并返回它们的总和。重复直到N
= 0.
我的第一个方法是使用它:
main = interact $ unlines . f . (map read) . lines
f::[Int] -> [String]
f (n:ls)
| n == 0 = []
| otherwise = [show rr] ++ (f rest)
where (xs, rest) = splitAt n ls
rr = sum xs
f _ = []
但它相对较慢。我用
分析了它ghc -O2 --make test.hs -prof -auto-all -caf-all -fforce-recomp -rtsopts
time ./test +RTS -hc -p -i0.001 < input.in
其中input.in
是第一行为100k的测试输入,后跟100k随机数,后跟0.我们可以在下图中看到它正在使用O(N)内存:
已编辑:我原来的问题是比较两种类似的慢速方法。我已将其更新为与
下面的优化方法进行比较现在,如果我迭代地进行求和,而不是调用sum
,我会得到恒定的内存量
{-# LANGUAGE BangPatterns #-}
main = interact $ unlines . g . (map read) . lines
g::[Int] -> [String]
g (n:ls)
| n == 0 = []
| otherwise = g' n ls 0
g _ = []
g' n (l:ls) !cnt
| n == 0 = [show cnt] ++ (g (l:ls))
| otherwise = g' (n-1) ls (cnt + l)
我试图了解第一个例子中导致性能损失的原因。我猜想可以懒得评估一切吗?
答案 0 :(得分:3)
我不确切地知道导致差异的原因。但我可以告诉你:
Data.Map> sum [1 .. 1e8]
Out of memory.
Data.Map> foldl' (+) 0 [1 .. 1e8]
5.00000005e15
出于某种原因,sum = foldl (+) 0
,而不是foldl'
(使用撇号)。不同之处在于后者的功能更严格,因此几乎不使用任何内存。相比之下,懒惰版本就是这样做的:
sum [1..100]
1 + sum [2..100]
1 + 2 + sum [3..100]
1 + 2 + 3 + sum [4.100]
...
换句话说,它创造了一个巨大的表达,表示1 + 2 + 3 + ......然后,在最后,它试图评估所有。嗯,显然,这会占用大量内存。使用foldl'
代替foldl
,您可以立即添加 ,而不是毫无意义地将它们存储在RAM中。
您可能还希望使用ByteString
而非String
进行I / O操作;但懒惰的差异可能会让你自己大大加快速度。
答案 1 :(得分:1)
我认为懒惰是阻止你的第一版和第二版同等的原因。
考虑从输入&#34;数字&#34;
创建的结果1
garbage_here
2
3
5
0
第一个版本会给出一个结果列表[错误&#34; ...一些解析错误&#34;,8],您可以安全地查看第二个元素,而第二个版本会立即错误。似乎很难以流媒体方式实现第一个。
即使没有懒惰,从第一个版本到第二个版本可能比GHC可以处理的更多 - 它需要在元组的第一个元素{{1}上组合foldl/foldl'
的融合重写规则}}。 GHC已经only recently达到了可以融合splitAt
的程度。