Haskellデータ構造とガベージコレクターへの影響

スタンフォード暗号化コースの問題の1つを解決するには、Word64->整数対応表を作成し、その中にある要素の存在を数百万回確認し、新しい要素を追加する必要がありました。 解決策は明白です:ハッシュテーブル。 hoogleはData.HashTableを提案しました。プログラムは作成され、正常に動作し、すべてを忘れることができましたが、プロファイリングと最適化を練習したかったのです。



+ RTS -sstderrから開始すると、わずかなショックが発生しました。ほぼ半分の時間がガベージコレクションに費やされました。



ヒープに割り当てられた13,902,917,208バイト

GCでコピーされた3,275,041,156バイト

MUT時間26.69秒(40.10秒経過)

GC時間27.84秒(32.47秒経過)

GC時間の割合51.1%(44.7%経過)



ヒーププロファイラーと集合的マインドへの訴えは、ヒープ内のハッシュテーブルデータを計算の中間結果とインターリーブするとGCを混乱させ、内部テーブルを増やしてコピーするとパフォーマンスが完全に失われると説明しました。 中間データの量を減らすための読み取りコードの最適化ではほとんど何も得られなかったため、ハッシュテーブルを変更する必要があります。



まず、Data.HashTableは、Basic、Cuckoo、およびLinearの3つのハッシュテーブルに置き換えられました。 結果はやや驚くべきものでした。ベーシックとカッコウは全体の動作時間を変更しませんでしたが、同時にGCは最大70%の時間を費やし始めました。 ネイティブコードの動作が速くなり、残りの時間はGCにかかっていました。 メモリのコピーは大幅に少なくなりました。



基本:

ヒープに割り当てられた12,781,006,960バイト

GC中にコピーされた408,935,780バイト

MUT時間15.00秒(15.16秒経過)

GC時間43.29秒(43.79秒経過)

GC時間の割合74.3%(74.3%経過)



カッコウ:

ヒープに割り当てられた11,611,257,712バイト

GC中にコピーされた419,308,168バイト

MUT時間17.31秒(17.65秒経過)

GC時間38.08秒(38.42秒経過)

%GC時間68.7%(68.5%経過)



線形テーブルは、予想されるとおり、完全に異なるシナリオを対象としており、すぐに競合他社から離れます。

ヒープに割り当てられた12,547,294,380バイト

GC中にコピーされた17,997,812,764バイト

MUT時間26.65秒(27.16秒経過)

GC時間123.72秒(124.30秒経過)

GC時間の割合82.3%(82.1%経過)



テーブルとCuckooHashTableの最初の正しいサイズを示す読み取りコードの最適化がさらにいくつか行われ、自信を持ってリードしました。

ヒープに割り当てられた3,835,164,148バイト

GC中にコピーされた179,941,588バイト

MUT時間7.84秒(7.97秒経過)

GC時間20.05秒(20.18秒経過)

GC時間の割合71.9%(71.7%経過)



合計ランタイムはすでに半分になっていますが、GCで70%ですか? コンサバトリーで何かを変更する必要があります。

Data.Judyはjudyテーブル実装のラッパーであり、GCに完全には該当せず、ゲームのルールを決定的に変更します。

ヒープに割り当てられた5,366,256,252バイト

GC中にコピーされた3,725,456バイト

MUT時間8.99秒(9.12秒経過)

GC時間1.44秒(1.45秒経過)

%GC時間13.4%(経過13.3%)



ヒーププロファイルは、割り当てられたメモリの量がまったく変わらないことを示しています。水平な水平線、すべてのデータはGCアクセス外です。



3つの欠点:





残っているものは何ですか? もちろん、Data.Map。 IOなし、Cなし、きれいに見える真のhaskellコード。 結果は突然です:

ヒープに割り当てられた5,569,024,484バイト

GC中にコピーされた4,241,345,748バイト

MUT時間18.55秒(18.77秒経過)

GC時間12.66秒(13.07秒経過)

%GC時間40.6%(41.1%経過)



CuckooHashTableよりも10%だけ遅く、Data.HashTableとの比較はまったくありません。 「考える必要はありません、振るだけ」の既製のイラスト:完全に簡単なコードを書くことができ、損失がほとんど見えないのに、なぜこれらすべてのダンスですか?



そして、Data.HashMap.Strictを使用した順序付けられていないコンテナの最後のコードは、Data.Mapの即座の置き換えです。インポートと構造タイプを変更するだけで、残りは互換性があります。

ヒープに割り当てられた5,688,939,936バイト

GC中にコピーされた3,659,088,964バイト

最大227,864,212バイトの常駐(23サンプル(s))

MUT時間10.16秒(10.75秒経過)

GC時間8.93秒(9.61秒経過)

GC時間の割合46.8%(47.2%経過)



コピーされた多くの、プロファイル上の明確に目に見えるテーブルの再割り当て。 + RTS -A250Mを使用して、もう少し多くのメモリをスリップしてみましょう。

ヒープに割り当てられた5,663,421,460バイト

GC中にコピーされた887,689,168バイト

MUT時間11.40秒(11.70秒経過)

GC時間3.61秒(3.83秒経過)

%GC時間24.1%(24.7%経過)



多田! ジュディは足りませんが、他のすべてのオプションは残されています。 無料のボーナスとして、Data.Mapと同じバンズ:ハッシュテーブルのように、キーをハッシュ変換に書き込む必要はありません。モナドにすべてを入れる必要はありません。コードは2倍短くなります。



まあ、伝統的な結論:時期尚早な最適化はすべての悪の根源です。 正面のソリューションは、他のソリューションよりも高速です。



All Articles