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

时间:2012-07-25 23:07:22

标签: haskell

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

encodeFile "test" [0..10000000]

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


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

1 个答案:

答案 0 :(得分:9)

以下是如何定义列表的序列化:

instance Binary a => Binary [a] where
    put l  = put (length l) >> mapM_ put l

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

为了找出列表的长度,我们需要评估整个列表。 但我们不能垃圾收集它,因为它的元素是第二个需要的 第1部分}}。所以整个列表必须存储在内存之后 在元素序列化开始之前评估长度。

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

profile

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

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

mapM_ put l

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

您还可以在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 中包含安全功能:在序列化列表时计算长度,并检查最后的第一个参数。如果存在不匹配,则抛出错误。

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

相关问题