【问题标题】:Haskell lazy Bytestring words not lazy?Haskell 懒 Bytestring 字不懒?
【发布时间】:2013-09-05 10:16:27
【问题描述】:

我有以下 Haskell 程序,用于计算一串整数的最大和子串:

{-# LANGUAGE BangPatterns #-} {-# OPTIONS_GHC -O2 #-}
import Data.Functor
import Data.Maybe 
import Data.ByteString.Lazy.Char8 (getContents,lines,readInt,words)
import Prelude hiding (getContents,words,lines)

main = do
    cont <- words <$> getContents
    putStrLn $ show $ snd $ foldl opt (0,0) $ map (fst.fromJust.readInt) cont

opt (!c,!m) x = (max 0 (c+x),max m (c+x))

这个程序的问题在于它将整个文件读入内存。对应的没有BytesString的程序就没有这个问题:

{-# LANGUAGE BangPatterns #-} {-# OPTIONS_GHC -O2 #-}
import Data.Functor
import Data.Maybe 

main = do
    cont <- words <$> getContents
    putStrLn $ show $ snd $ foldl opt (0,0) $ map read cont
opt (!c,!m) x = (max 0 (c+x),max m (c+x))

它只使用少量的恒定内存,但当然速度非常慢(大约慢 25 倍)。

该问题仅发生在读取非常大行的程序中。如果输入分布在多条小行上,ByteString 会按预期执行。

有没有办法解决这个问题?

【问题讨论】:

  • 我不得不承认我无法再重现这个(只要我启用优化)...
  • 您是否总是将 OPTIONS_GHC 与其他 pragma 放在同一行?这可能无法真正实现优化。

标签: haskell bytestring long-lines lazy-io


【解决方案1】:

惰性元组的使用是次优的。最好重写为:

main = do
    cont <- words <$> getContents
    putStrLn $ show $ sndT $ foldl opt (T 0 0) $ map (fst.fromJust.readInt) cont

sndT :: T -> Int
sndT (T _ m) = m

opt (T c m) x = T (max 0 (c+x)) (max m (c+x))

data T = T {-# UNPACK #-} !Int  {-# UNPACK #-}!Int

所以你得到了一个严格的、未装箱的累加器。然而,你最好把整个事情写成一个增量的左折叠。这就是readInt 在其第二个参数中返回剩余输入的原因。不需要总和。地图 。单词管道。


您提交的版本泄漏空间。在大文件上运行,它使用与文件大小成比例的堆(在 640k 条目上)。

$ time ./A +RTS -p -s -K50M < input.txt.2
346882
     326,337,136 bytes allocated in the heap
     302,321,732 bytes copied during GC
      82,617,772 bytes maximum residency (8 sample(s))
       1,466,500 bytes maximum slop
             149 MB total memory in use (0 MB lost due to fragmentation)

  %GC     time      63.8%  (63.9% elapsed)

正如你所说,它保留了文件。

那么什么是保留记忆?一条线索是带有opt 的折叠。你传递给它一个惰性元组。而foldllazy in its accumulator

因此,您只是在构建一长串未经评估的 + 操作。 opt 上的 bang 模式没有区别,因为 foldl 从不评估其累加器。只有当你最终检查结果时,整个事情才会崩溃。

这是典型的空间泄漏。所以:

  • 使用 foldl' -- 在累加器中是严格的
  • 完全避免中间列表(使用 readInt + 展开器)。
  • 如果您必须遍历列表,请在累加器上使用严格元组以获得更好的性能。

【讨论】:

  • 谢谢你,效果很好。但是我仍然不明白为什么我的元组会导致空间泄漏。你能告诉我吗?
  • 我绝对不明白:为什么我的程序的 ByteString 版本只泄漏空间?
  • 您的解释不能成为观察到的行为的原因,因为 1. 我试过 foldl' 并没有任何区别 2. String 版本从不泄漏空间 3. 程序可以处理 100M数据将是未评估的堆栈溢出+
  • 现在我完全糊涂了,我无法重现自己的问题...??我提供的 ByteString 程序刚刚消耗了一个 LARGE 文件,而没有将它保留在我计算机的内存中。我不认为有任何软件更新或任何东西。
【解决方案2】:

map (fst.fromJust.readInt) cont

不应该这样吗

main = do
    cont <- getContents
    putStrLn $ show $ snd $ foldl opt (0,0) $
               unfoldr (readInt . dropWhile isSpace) cont

【讨论】:

  • 我还必须去掉中间的空格,所以更像unfoldr (readInt . dropWhile (==' ')),但谢谢,我不知道展开器。
  • 是的,抱歉,我意识到了这一点,但看起来编辑太晚了
猜你喜欢
  • 2015-09-06
  • 1970-01-01
  • 2015-09-06
  • 2021-08-12
  • 1970-01-01
  • 2016-12-14
  • 1970-01-01
  • 1970-01-01
  • 2012-08-25
相关资源
最近更新 更多