【问题标题】:Reading large file in haskell?在haskell中读取大文件?
【发布时间】:2011-07-21 14:42:51
【问题描述】:

我一直在尝试在 haskell 中读取一个大文件。

我需要为大学项目使用自定义算法对其进行压缩。在我开始压缩大文件之前一切正常。

我从程序中提取出了问题所在,并在此处以“Hello big file”的形式将其公开:

import System
import qualified Data.ByteString.Lazy as BL
import Data.Word

fold_tailrec :: (a -> b -> a) -> a -> [b] -> a
fold_tailrec _ acc [] =
    acc
fold_tailrec foldFun acc (x : xs) =
    fold_tailrec foldFun (foldFun acc x) xs

fold_tailrec' :: (a -> b -> a) -> a -> [b] -> a
fold_tailrec' _ acc [] =
    acc
fold_tailrec' foldFun acc (x : xs) =
    let forceEval = fold_tailrec' foldFun (foldFun acc x) xs in
    seq forceEval forceEval

main :: IO ()
main =
    do
        args <- System.getArgs
        let filename = head args
        byteString <- BL.readFile filename
        let wordsList = BL.unpack byteString
        -- wordsList is supposed to be lazy (bufferized)
        let bytesCount = fold_tailrec (\acc word -> acc + 1) 0 wordsList
        print ("Total bytes in " ++ filename ++ ": " 
               ++ (show bytesCount))

我将此文件命名为 Test.hs,然后执行以下操作:

$ ls -l toto
-rwxrwxrwx 1 root root 5455108 2011-03-23 19:08 toto
$ ghc --make -O Test.hs
[1 of 1] Compiling Main             ( Test.hs, Test.o )
Linking Test ...
$ ./Test toto
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.
$ ./Test toto +RTS -K50M -RTS
Stack space overflow: current size 50000000 bytes.
Use `+RTS -Ksize -RTS' to increase it.
$ ./Test toto +RTS -K500M -RTS
"Total bytes in toto: 5455108"
$ time ./Test toto +RTS -K500M -RTS
"Total bytes in toto: 5455108"

real    0m33.453s
user    0m8.917s
sys 0m10.433s

谁能解释一下为什么我需要 500 兆字节的 RAM 和 30 秒的 CPU 才能浏览一个可怜的 5 兆字节文件?请问我在做什么错?为什么 [word8] 没有像 ByteString 文档所述那样缓冲。以及如何解决这个问题?

我尝试定义自己的尾递归折叠而不是 foldl、foldr 或 foldl'。 我也尝试使用 seq 解冻 thunk。 到目前为止我没有得到任何结果。

感谢您的任何帮助,因为我被困住了。

【问题讨论】:

  • @FUZxxl,想必这是一个简单的复制器;如果我们也有他的压缩代码要处理,那么对程序进行推理就会困难得多。如果 Joel 真正想要的只是长度,不妨用stat(2) 询问操作系统并保存所有磁盘 IO。
  • 你需要自己的折叠做什么?标准库中的那些应该可以正常工作。由于懒惰,尾递归在 Haskell 中并不总是正确的。我认为您想要计算长度的是严格的左折叠,这在累加器中也是严格的。您应该能够通过 foldl' 和可能的 seq 获得它。
  • @FUZxxl length 会导致内存泄漏。您必须将整个内容保存在内存中,因为在调用 length 之前无法对其进行垃圾收集。
  • 我建议你看看 enumerator 包而不是使用惰性 IO。
  • 您好,这只是一个例子。我的代码分散到许多模块中,处理压缩的东西。在这里,我从我的程序中提取出了问题所在。我不需要使用自己的折叠,我只是表明我尝试了尾递归,然后是严格性,但并没有解决问题。而且我不想计算文件大小,但我需要逐字节浏览它,如示例中所示。如果你能提供帮助,谢谢。

标签: io lazy-evaluation bytestring haskell


【解决方案1】:

构造“seq x x”总是没用的。如果 y = seq x x 并且我强制 y 那么这强制 x 然后返回 x。这等效于 y=x 并强制 y。因此,“seq forceEval forceEval”只不过是“forceEval”。

您使用折叠的错误很常见。

您正在使用折叠来计算输入中的字节数。对于这样的总和,您应该使用严格的左折叠,但您的手写折叠是惰性左折叠。 (acc+1) 没有得到评估,因此它构建了 500 万个嵌套应用程序: (((...(0+1)+1)+1)+1)+1)+1)...+1 )。然后在打印的时候强制,求值尽量下降到500万个括号。

因此,待处理堆栈对于每个 Word8 都有一个条目。对于短输入,它会到达末尾并看到 0。对于长输入,它会用 GHC 耗尽堆栈空间,因为 GHC 的创建者和大多数用户认为尝试分配 500 万个堆栈帧通常是程序员的设计错误。

我预测你可以使用“seq”来解决这个问题:

fold_tailrec' foldFun acc (x : xs) =
    let acc' = foldFun acc x
    in seq acc' (fold_tailrec' foldFun acc' xs)

【讨论】:

  • 非常感谢,它有效!我是这里的新手,请问如何将问题标记为已解决?
  • @Joel:答案的左上角应该有一个复选标记的轮廓,就在答案的分数下方。单击此处标记为已解决。
  • 他们可能会为这种情况添加警告。
  • 此处有更深入的解释:haskell.org/haskellwiki/Foldr_Foldl_Foldl'(简短摘要:您几乎总是想要foldrfoldl')。
猜你喜欢
  • 2011-12-13
  • 1970-01-01
  • 2015-01-28
  • 2012-06-16
  • 2015-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多