2015-11-05 14 views
14

私はハスケルでソフトリアルタイムのアプリケーションを書いています。これは、シミュレートされた物理、衝突検出、すべての良いことを扱っています。私はたくさんのメモリを割り当てていますが、私が望むなら私はおそらく私のメモリ使用量を最適化することができます。しかし、40%のCPUと1%のRAMしか使用していないので、私が見ていることは、ガベージコレクタが起動する時間が長いことです。フレームはスキップされます。私はthreadscopeでプロファイリングすることで問題の原因であることを確認しました。ガベージコレクタがそのビジネスを行っている間に、時には0.05秒まで有効な計算が行われず、スキップされたフレームが3つまで発生します。 。Haskellでソフトリアルタイムアプリケーションのガベージコレクションを最適化するにはどうすればよいですか?

今、私は手動ですべてのフレームperformMinorGCを呼び出すことによってこれを解決しようとしたが、これは離れて、全体的なCPU使用率が約70%まで大幅に行くという事実から、それは非常にスムーズになって、問題を軽減するように思われます。明らかに私はむしろこれを避けるだろう。

もう1つのことは、-H64kを使用してGCの割り当て領域を512kから64kに減らし、-I0.03を設定してより頻繁に収集するようにしてみました。これらのオプションはどちらも、私がthreadscopeで見たガベージコレクションのパターンを変更しましたが、それでもスキップされたフレームが発生しました。

GC最適化に関する経験がある方は、ここで私を助けることができますか?私は手動でperformMinorGCと呼ぶことになりました。

EDIT

は、私はこれらの試験で同様の時間のためにそれを実行しようとしたが、それはリアルタイムなので、それが「行って」いたのも意味がありません。 performMinorGCすべての4コマで

ランタイム統計:全体的な生産性は、私はいくつかの理由で、昨日それをテストしたときよりも、今なしperformMinorGCため低いように思わなしperformMinorGC

12,316,488,144 bytes allocated in the heap 
    447,495,936 bytes copied during GC 
     63,556,272 bytes maximum residency (15 sample(s)) 
     15,418,296 bytes maximum slop 
      146 MB total memory in use (0 MB lost due to fragmentation) 

            Tot time (elapsed) Avg pause Max pause 
    Gen 0  19292 colls, 19292 par 2.613s 0.950s  0.0000s 0.0161s 
    Gen 1  15 colls, 14 par 0.237s 0.165s  0.0110s 0.0499s 

    Parallel GC work balance: 2.67% (serial 0%, perfect 100%) 

    TASKS: 17 (2 bound, 13 peak workers (15 total), using -N4) 

    SPARKS: 100714 (29688 converted, 0 overflowed, 0 dud, 47577 GC'd, 23449 fizzled) 

    INIT time 0.000s ( 0.001s elapsed) 
    MUT  time 13.377s ( 9.917s elapsed) 
    GC  time 2.850s ( 1.115s elapsed) 
    EXIT time 0.000s ( 0.006s elapsed) 
    Total time 16.247s (11.039s elapsed) 

    Alloc rate 920,744,995 bytes per MUT second 

    Productivity 82.5% of total user, 121.4% of total elapsed 

gc_alloc_block_sync: 68533 
whitehole_spin: 0 
gen[0].sync: 9 
gen[1].sync: 147 

 9,776,109,768 bytes allocated in the heap 
    349,349,800 bytes copied during GC 
     53,547,152 bytes maximum residency (14 sample(s)) 
     12,123,104 bytes maximum slop 
      105 MB total memory in use (0 MB lost due to fragmentation) 

            Tot time (elapsed) Avg pause Max pause 
    Gen 0  15536 colls, 15536 par 3.033s 0.997s  0.0001s 0.0192s 
    Gen 1  14 colls, 13 par 0.207s 0.128s  0.0092s 0.0305s 

    Parallel GC work balance: 6.15% (serial 0%, perfect 100%) 

    TASKS: 20 (2 bound, 13 peak workers (18 total), using -N4) 

    SPARKS: 74772 (20785 converted, 0 overflowed, 0 dud, 38422 GC'd, 15565 fizzled) 

    INIT time 0.000s ( 0.001s elapsed) 
    MUT  time 9.773s ( 7.368s elapsed) 
    GC  time 3.240s ( 1.126s elapsed) 
    EXIT time 0.003s ( 0.004s elapsed) 
    Total time 13.040s ( 8.499s elapsed) 

    Alloc rate 1,000,283,400 bytes per MUT second 

    Productivity 75.2% of total user, 115.3% of total elapsed 

gc_alloc_block_sync: 29843 
whitehole_spin: 0 
gen[0].sync: 11 
gen[1].sync: 71 

- - それが常に> 90%になる前に。

+1

ランタイム統計情報( '+ RTS -s') – Yuras

+1

素朴な提案を貼り付けてください。しかし、単にperformMinorGCを10フレームごとに呼び出すとどうなりますか? –

+1

あなたは何を割り当てていますか?割り当てを避けることができれば、GCは問題になりません。 – MathematicalOrchid

答えて

4

大規模な古い世代があります。それは100Mbほどの大きさです。

デフォルトでは、GHCは、ヒープサイズが最後のメジャーGC後のサイズの2倍に達したときにメジャーGCを実行します。つまり、ある時点で、GCは50Mbのデータをスキャンしてコピーする必要がありました。プロセッサに10Gbのメモリスループットの制限がある場合、50Mbのロードとコピーには少なくとも0.01秒かかります(gen1の平均と最大のポーズと比較して)。

(私はあなたがイベントログをチェックして、 0.05sec一時停止。したがって、GCが実際の作業を行うのではなく、他のスレッドを待っているときにスレッドの同期に問題はありません)。

GCの一時停止を最小限に抑えるには、この50Mbの大半が最初に割り当てられた静的データで、最後まで(テクスチャやメッシュなど)はstuckです。私が知っている唯一の回避策は、あなたがそれを必要とするときにもう一度その部分を解凍してください。

実行中にデータが割り当てられ、限られた時間が経過しても(ただし、主要世代はほとんど生き残ることができない場合)、パイプラインの再考を試みます。通常、データは1つのフレームで存続する必要がありませんので、何か問題があります。例えば。必要がないときにデータを保持します。

その他の悪いサイン - gen0 max pause 0.02秒。それはかなり変です。デフォルトではgen0の割り当て領域は0.5Mbなのでgen0 GCは高速になるはずです。多分あなたは大きいremembered setを持っています。考えられる原因:可変構造(IORef、可変ベクトルなど)または多くの遅延サンクの更新。

マイナーな(おそらく関係のない)問題:暗黙の並列処理を使用しているようですが、スパークの1/3しか変換されません。あなたはあまりにも多くのスパートを割り当てています、それらの1/2はGCです。

+0

これに基づいて、私はいくつかの実験を行い、メインループの回転を除いてすべてのロジックコードをコメントアウトしてしまった。私がそれをすれば、私は最大3.3GB/sの割り振り速度まで進んでいくことが分かります。だから、私がメインループを実装したやり方は、すべての繰り返しでメモリの負荷が割り当てられていることを意味します... GCはそれほど多くの争点を抱えているので、GCがこのような大きな休止を引き起こしているのではないでしょうか!私はそれが私の変圧器のスタックとは何かでなければならないと思う。私は問題を切り離そうとします。私はそれが間違っていると私に言ったので、答えを受け入れるでしょう。 –

+0

更新: 'threadDelay 100'をメインループに張り付けると、問題が大幅に緩和されました。 私は別のスレッドでレンダリングを行っているので、メインループはほとんどの繰り返しで実際の作業をせずに、周りを回転していました。 'threadDelay'は少しハックのようですが... –

関連する問題