6

メモリ リークが含まれていると思われるライブラリのごく一部を特定しました。以下のコードは、実際のコードと同じ結果を生成しながら、できる限り小さくしています。

import System.Random
import Control.Monad.State
import Control.Monad.Loops
import Control.DeepSeq
import Data.Int (Int64)
import qualified Data.Vector.Unboxed as U

vecLen = 2048

main = flip evalStateT (mkStdGen 13) $ do
    let k = 64
    cs <- replicateM k transform
    let sizeCs = k*2*7*vecLen*8 -- 64 samples, 2 elts per list, each of len 7*vecLen, 8 bytes per Int64
    (force cs) `seq` lift $ putStr $ "Expected to use ~ " ++ (show ((fromIntegral sizeCs) / 1000000 :: Double)) ++ " MB of memory\n"

transform :: (Monad m, RandomGen g)
           => StateT g m [U.Vector Int64]
transform = do
      e <- liftM ((U.map round) . (uncurry (U.++)) . U.unzip) $ U.replicateM (vecLen `div` 2) sample
      c1 <- U.replicateM (7*vecLen) $ state random
      return [U.concat $ replicate 7 e, c1]

sample :: (RandomGen g, Monad m) => StateT g m (Double, Double)
sample = do 
    let genUVs = liftM2 (,) (state $ randomR (-1,1)) (state $ randomR (-1,1))
        -- memory usage drops and productivity increases to about 58% if I set the guard to "False" (the real code needs a guard here)
        uvGuard (u,v) = u+v >= 2 -- False -- 
    (u,v) <- iterateWhile uvGuard genUVs
    return (u, v)

コードをさらに削除すると、メモリ使用量/GC、時間、またはその両方のパフォーマンスが大幅に向上します。ただし、上記のコードを計算するには が必要なので、実際のコードはこれ以上単純にはなりません。たとえば、e と c1 の両方が から値を取得するようにするとsample、コードは 27 MB のメモリを使用し、GC で 9% の実行時間を費やします。e と c1 の両方state randomを使用すると、約 400MB のメモリが使用され、実行時間の 32% しか GC に費やされません。

主なパラメータはvecLenで、実際には 8192 前後が必要です。プロファイリングを迅速に行うために、以下のすべての結果を で生成しましたvecLen=2048が、値が大きくなるほど問題はさらに悪化しvecLenます。

でコンパイル

ghc test -rtsopts

私は得る:

> ./test +RTS -sstderr
Working...
Expected to use ~ 14.680064 MB of memory
Done
   3,961,219,208 bytes allocated in the heap
   2,409,953,720 bytes copied during GC
     383,698,504 bytes maximum residency (17 sample(s))
       3,214,456 bytes maximum slop
             869 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      7002 colls,     0 par    1.33s    1.32s     0.0002s    0.0034s
  Gen  1        17 colls,     0 par    1.60s    1.84s     0.1080s    0.5426s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    2.08s  (  2.12s elapsed)
  GC      time    2.93s  (  3.16s elapsed)
  EXIT    time    0.00s  (  0.03s elapsed)
  Total   time    5.01s  (  5.30s elapsed)

  %GC     time      58.5%  (59.5% elapsed)

  Alloc rate    1,904,312,376 bytes per MUT second

  Productivity  41.5% of total user, 39.2% of total elapsed


real    0m5.306s
user    0m5.008s
sys 0m0.252s

-p または -h* を使用してプロファイリングしても、少なくとも私にはあまりわかりません。

ただし、スレッドスコープは興味深いものです。スレッドスコープ

ヒープを吹き飛ばしているように見えるので、GC が発生し、ヒープ サイズが 2 倍になっています。実際、-H4000M を指定して実行すると、スレッドスコープはわずかに均一に見えますが (2 倍の作業、2 倍の GC が少なくなります)、それでも実行時間全体の約 60% を GC に費やしています。-O2 を指定したコンパイルはさらに悪く、実行時間の 70% 以上が GC に費やされます。

質問: 1. なぜ GC はそれほど多く実行されているのですか? 2.ヒープの使用量が予想外に多いですか? もしそうなら、なぜですか?

質問 2 については、ヒープ使用量が「予想される」メモリ使用量を大幅に超える可能性があることを認識しています。しかし、800MB は私には過剰に思えます。(それは私が見るべき数でもありますか?)

4

1 に答える 1