2012-07-25 52 views
6

在GHCI,我跑这个简单的测试:为什么Data.Binary的encodeFile不起作用?

encodeFile "test" [0..10000000] 

线真的跑得快(< 10秒),但我的内存使用量最多拍摄〜500MB完成之前。不应该因为它使用ByteString.Lazy而使文件变得懒惰?


编辑:罗马的答案很好!我还想指出this answer另一个问题,这解释了为什么Data.Binary在列表上执行严格的编码并提供了稍微优雅的工作。

+0

一般来说,GHCi不适合任何形式的分析。它没有进行任何优化并解释Haskell代码,所以内存使用情况和性能特征通常与编译后的Haskell完全不同。你应该用'ghc -O2'进行编译,看看问题是否重复。 – shang 2012-07-26 05:56:51

+0

我在用ghc -O2编译的程序中发现了问题,并将其解决。 – 2012-07-26 17:01:22

回答

9

这里是如何列出的序列化的定义:

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

也就是说,第一序列名单的长度,然后序列化列表本身。

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

这里的堆轮廓看起来像:

profile

注意它是如何增长的,而列表是被建立在计算它的长度, 然后降低,而元素是序列化,可以通过收集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 

这个程序中的堆空间53K运行。

您还可以在putWithLength中包含安全功能:在序列化列表时计算长度,并在最后检查第一个参数。如果不匹配,请输入错误。

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

+0

用于显示堆配置文件的+1(当然是正确的) – alternative 2012-07-26 11:54:48

+0

练习答案:因为格式要求长度是编码中的第一项,但直到最后才会有计算的长度。 – 2012-07-26 17:03:12