Performance 提高分块列表的性能

Performance 提高分块列表的性能,performance,haskell,Performance,Haskell,我有一个简单的问题:给定一个整数列表,将第一行读为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

我有一个简单的问题:给定一个整数列表,将第一行读为
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


在第一个示例中,我试图理解是什么导致了性能损失。我猜那里的一切都可以懒散地评估?

我不知道到底是什么造成了这种差异。但我可以告诉你:

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操作;但是懒惰的不同可能会给你一个巨大的速度提升。

我认为懒惰是阻止你的第一个版本和第二个版本成为同等版本的原因

考虑从输入“数字”创建的结果

第一个版本会给出一个结果列表[error“…some parse error”,8],您可以安全地查看的第二个元素,而第二个版本会立即出现错误。似乎很难以流式方式实现第一个


即使没有懒惰,从第一个版本到第二个版本也可能超出GHC的处理能力——它需要在元组的第一个元素上结合
foldl/foldl'
splitAt
的融合重写规则。GHC已经到了一个可以完全融合
foldl/foldl'
的地步。

谢谢!我认为我尝试的方法确实是低效的,因为它试图将整个数据加载到内存中。我已经更新了一个优化的方法来比较差异。我仍然不确定为什么第一种方法不能像第二种方法那样处理streamlined。谢谢你的建议。顺便说一句,ByteString实际上是我所需要的。我尝试过的想法只是改善了内存占用,而不是速度。未经测试的猜测:可能第一个示例中对
read
的两次调用没有使用相同的类型,例如,存在一些
Integer
vs
Int
问题。(编辑:这个评论是关于这个问题的前一个版本)是的,我认为如果不以更聪明的方式重写它,编译器就无法真正理解:(
sum [1..100]
1 + sum [2..100]
1 + 2 + sum [3..100]
1 + 2 + 3 + sum [4.100]
...
1
garbage_here
2
3
5
0