代码之家  ›  专栏  ›  技术社区  ›  Cactus

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

  •  3
  • Cactus  · 技术社区  · 15 年前

    在尝试用Java类文件语言导入基本Java运行时库rt.jar之后,我发现它使用了大量内存。

    我已经把演示这个问题的程序减少到100行并上传到 hpaste . 不强制评估 stream 在第94行中,我没有机会运行它,因为它耗尽了我所有的记忆。强迫 流动 在把它交给 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公司 )留在记忆里?

    3 回复  |  直到 15 年前
        1
  •  2
  •   John L    15 年前

    编辑2:我刚刚意识到你的代码做到了这一点:

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

    别那么做。这个 pack 功能是出了名的慢。你需要找到一个不涉及使用 包装 创建ByteString。

    我留下剩下的答案,因为我仍然认为它适用,但这几乎肯定是最大的问题。

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

    如果你只想得到结果 cls 为了保留在内存中,为什么不强制它而不是强制流?将第94行更改为

    cls `seq` return cls
    

    可能需要使用 deepseq 而不是仅仅 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公司 在内部使用,从不返回,因此在 形式_ .

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

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

        2
  •  1
  •   jmg    15 年前

    你强迫在第94行中评估cls的想法是正确的。但我想你这样做并没有成功。看这个 paste 对于我的版本,它运行在大约40MB而不是220MB。

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

    rnf cls`seq`返回cls

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

        3
  •  0
  •   Cactus    15 年前

    谢谢你的建议。

    我认为对于我的具体问题,解决方案是将.jar文件分成小块处理——幸运的是,内部类在.jar文件中始终与外部类位于同一个目录中,因此不需要在一次运行中处理所有50兆字节。

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

    推荐文章