为什么Data.Binary的encodeFile不会表现得很懒惰?

Mik*_*cki 6 haskell

在GHCI中,我运行这个简单的测试:

encodeFile "test" [0..10000000]
Run Code Online (Sandbox Code Playgroud)

该线路运行速度非常快(<10秒),但我的内存使用量在完成之前会达到~500MB.不应该编码文件是懒惰的,因为它使用ByteString.Lazy?


编辑:罗曼的答案很棒!我还想指出另一个问题的答案,这解释了为什么Data.Binary对列表进行严格编码并提供稍微优雅的解决方法.

Rom*_*aka 9

以下是定义列表序列化的方法:

instance Binary a => Binary [a] where
    put l  = put (length l) >> mapM_ put l
Run Code Online (Sandbox Code Playgroud)

也就是说,首先序列化列表的长度,然后序列化列表本身.

为了找出列表的长度,我们需要评估整个列表.但是我们不能对它进行垃圾收集,因为第二部分需要它的元素mapM_ put l.因此,在评估长度之后和元素序列化开始之前,必须将整个列表存储在内存中.

以下是堆配置文件的外观:

轮廓

注意在构建列表以计算其长度时它是如何增长的,然后在元素被序列化时减少,并且可以由GC收集.

那么,如何解决这个问题呢?在您的示例中,您已经知道了长度.因此,您可以编写一个采用已知长度的函数,而不是计算它:

import Data.Binary
import Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import Data.Binary.Put

main = do
  let len = 10000001 :: Int
      bs = encodeWithLength len [0..len-1]
  L.writeFile "test" bs

putWithLength :: Binary a => Int -> [a] -> Put
putWithLength len list =
  put len >> mapM_ put list

encodeWithLength :: Binary a => Int -> [a] -> ByteString
encodeWithLength len list = runPut $ putWithLength len list
Run Code Online (Sandbox Code Playgroud)

该程序在53k的堆空间内运行.

您还可以在以下方面包含安全功能putWithLength:在序列化列表时计算长度,并在最后检查第一个参数.如果存在不匹配,则抛出错误.

练习:为什么你仍然需要传递长度putWithLength而不是使用如上所述的计算值?