强制在Haskell的Data.Binary.Get中进行顺序处理

时间:2010-11-16 18:15:37

标签: performance haskell lazy-evaluation

在尝试使用language-java-classfile导入基本Java运行时库rt.jar后,我发现它使用了大量内存。

我已将显示问题的程序缩减为100行并将其上传到hpaste。在没有强制对第94行stream进行评估的情况下,我没有机会运行它,因为它占用了我所有的记忆。在将stream传递给getClass之前强制 34,302,587,664 bytes allocated in the heap 32,583,990,728 bytes copied during GC 139,810,024 bytes maximum residency (398 sample(s)) 29,142,240 bytes maximum slop 281 MB total memory in use (4 MB lost due to fragmentation) Generation 0: 64992 collections, 0 parallel, 38.07s, 37.94s elapsed Generation 1: 398 collections, 0 parallel, 25.87s, 27.78s elapsed INIT time 0.01s ( 0.00s elapsed) MUT time 37.22s ( 36.85s elapsed) GC time 63.94s ( 65.72s elapsed) RP time 0.00s ( 0.00s elapsed) PROF time 13.00s ( 13.18s elapsed) EXIT time 0.00s ( 0.00s elapsed) Total time 114.17s (115.76s elapsed) %GC time 56.0% (56.8% elapsed) Alloc rate 921,369,531 bytes per MUT second Productivity 32.6% of total user, 32.2% of total elapsed 完成,但仍占用大量内存:

ConstTable

我认为问题是cls停留在那里,所以我也尝试在#94行强制 34,300,700,520 bytes allocated in the heap 23,579,794,624 bytes copied during GC 487,798,904 bytes maximum residency (423 sample(s)) 36,312,104 bytes maximum slop 554 MB total memory in use (10 MB lost due to fragmentation) Generation 0: 64983 collections, 0 parallel, 71.19s, 71.48s elapsed Generation 1: 423 collections, 0 parallel, 344.74s, 353.01s elapsed INIT time 0.01s ( 0.00s elapsed) MUT time 40.60s ( 42.38s elapsed) GC time 415.93s (424.49s elapsed) RP time 0.00s ( 0.00s elapsed) PROF time 56.53s ( 57.71s elapsed) EXIT time 0.00s ( 0.00s elapsed) Total time 513.07s (524.58s elapsed) %GC time 81.1% (80.9% elapsed) Alloc rate 844,636,801 bytes per MUT second Productivity 7.9% of total user, 7.7% of total elapsed 。但这只会使内存消耗和运行时更糟:

cls

所以我的问题基本上是,如何强制顺序处理所涉及的文件,以便在处理完每个文件后,只有字符串结果({{1}})保留在内存中?

3 个答案:

答案 0 :(得分:2)

编辑2:我刚刚意识到你的代码是这样做的:

stream <- BL.pack <$> fileContents [] classfile

不要那样做。 pack函数非常慢。您需要找到一个不涉及使用pack创建ByteString的解决方案。

我将离开我的其余部分,因为我仍然认为它适用,但这几乎肯定是最大的问题。

不幸的是我不能测试这个,因为我不认识你所有的进口。

如果您只希望结果cls保留在内存中,为什么不强制它而不是强制流?将第94行更改为

cls `seq` return cls

可能有必要使用deepseq而不仅仅是seq,尽管我怀疑普通seq就足够了。

但是我认为有更好的解决方案,那就是使用mapM_代替mapM。我认为通常更好的样式(并且几乎总是更好的性能)来创建一个函数,该函数对每个结果执行它应该执行的操作而不是返回列表。在这里,您可以将主要功能更改为:

main = do 
  withArchive [CheckConsFlag] jarPath $ do
    classfiles <- filter isClassfile <$> fileNames []
    forM_ classfiles $ \classfile -> do 
      stream <- BL.pack <$> fileContents [] classfile
      let cls = runGet getClass stream
      lift $ print cls

现在print被提升到每个类文件传递给forM_的函数中。值cls在内部使用且永远不会返回,因此它在forM_的每次迭代中都进行了全面评估并快速GC。

在更大的应用程序中使用这种风格可能需要进行一些重构甚至重新设计,但结果可能是值得的。

编辑:如果您在重新设计代码时遇到麻烦,可以使用迭代器并完全避免此问题。

答案 1 :(得分:1)

你想在第94行强制评估cls的想法是正确的。但我猜你这样做的方法并不成功。请参阅此paste了解我的版本,该版本以ca. 40MB而不是220MB。

关键是强制减少正常形式的cls,这是由rnf cls完成的。这必须在回电之前发生。因此:

rnf cls`seq` return cls

或者,您可以使用Control.Exception.evaluate:   评估$ rnf cls   返回cls

答案 2 :(得分:0)

感谢您的建议。

我认为对于我的具体问题,解决方案是以小块处理.jar文件 - 幸运的是,内部类总是与.jar文件中的外部类在同一目录中,所以没有必要在一次运行中处理所有50兆。

我唯一不能理解的是,是否可以通过枚举器使用libzip,还是需要新的libzip实现?