メモリ リークが含まれていると思われるライブラリのごく一部を特定しました。以下のコードは、実際のコードと同じ結果を生成しながら、できる限り小さくしています。
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 は私には過剰に思えます。(それは私が見るべき数でもありますか?)