在尝试使用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}})保留在内存中?
答案 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实现?