「メッセージ」を受信して転送するプログラムを開発していますが、メッセージの一時的な履歴を保持しているので、要求に応じてメッセージの履歴を伝えることができます。メッセージは数値で識別され、通常は約1キロバイトのサイズであり、数十万のメッセージを保持する必要があります。
このプログラムを遅延に対して最適化したいと思います。メッセージの送信と受信の間の時間は10ミリ秒未満でなければなりません。
プログラムはHaskellで書かれており、GHCでコンパイルされています。ただし、ガベージコレクションの一時停止は、待機時間の要件に対して長すぎることがわかりました。実際のプログラムでは100ミリ秒を超えています。
次のプログラムは、アプリケーションの簡易バージョンです。Data.Map.Strict
メッセージの保存にを使用します。メッセージはでByteString
識別されますInt
。1,000,000メッセージは昇順で挿入され、最も古いメッセージは継続的に削除されて履歴が最大200,000メッセージに保たれます。
module Main (main) where
import qualified Control.Exception as Exception
import qualified Control.Monad as Monad
import qualified Data.ByteString as ByteString
import qualified Data.Map.Strict as Map
data Msg = Msg !Int !ByteString.ByteString
type Chan = Map.Map Int ByteString.ByteString
message :: Int -> Msg
message n = Msg n (ByteString.replicate 1024 (fromIntegral n))
pushMsg :: Chan -> Msg -> IO Chan
pushMsg chan (Msg msgId msgContent) =
Exception.evaluate $
let
inserted = Map.insert msgId msgContent chan
in
if 200000 < Map.size inserted
then Map.deleteMin inserted
else inserted
main :: IO ()
main = Monad.foldM_ pushMsg Map.empty (map message [1..1000000])
このプログラムをコンパイルして実行しました。
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.10.3
$ ghc -O2 -optc-O3 Main.hs
$ ./Main +RTS -s
3,116,460,096 bytes allocated in the heap
385,101,600 bytes copied during GC
235,234,800 bytes maximum residency (14 sample(s))
124,137,808 bytes maximum slop
600 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 6558 colls, 0 par 0.238s 0.280s 0.0000s 0.0012s
Gen 1 14 colls, 0 par 0.179s 0.250s 0.0179s 0.0515s
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.652s ( 0.745s elapsed)
GC time 0.417s ( 0.530s elapsed)
EXIT time 0.010s ( 0.052s elapsed)
Total time 1.079s ( 1.326s elapsed)
%GC time 38.6% (40.0% elapsed)
Alloc rate 4,780,213,353 bytes per MUT second
Productivity 61.4% of total user, 49.9% of total elapsed
ここで重要なメトリックは、0.0515秒、つまり51ミリ秒の「最大休止」です。これを少なくとも1桁削減したいと考えています。
実験により、GCの一時停止の長さは履歴内のメッセージ数によって決まることが示されています。関係はおおよそ線形、またはおそらく超線形です。次の表は、この関係を示しています。(ここで私たちのベンチマークテストを見ることができ、ここでいくつかのチャートを見ることができます。)
msgs history length max GC pause (ms)
=================== =================
12500 3
25000 6
50000 13
100000 30
200000 56
400000 104
800000 199
1600000 487
3200000 1957
6400000 5378
他のいくつかの変数を試して、このレイテンシを短縮できるかどうかを確認しましたが、どれも大きな違いはありません。これらの重要でない変数の中には:最適化(-O
、-O2
); RTS GCオプション(-G
、-H
、-A
、-c
)、コア(の数-N
)、異なるデータ構造(Data.Sequence
)、メッセージのサイズ、および生成された短命のごみの量。圧倒的な決定要因は、履歴内のメッセージの数です。
私たちの作業理論は、各GCサイクルがすべての作業中のアクセス可能なメモリをウォークスルーしてコピーする必要があるため、一時停止はメッセージ数において線形であり、それは明らかに線形操作です。
質問:
- この線形時間理論は正しいですか?GCポーズの長さをこの単純な方法で表現できますか、それとも現実はより複雑ですか?
- 作業メモリ内でGCの一時停止が線形である場合、関連する一定の要因を減らす方法はありますか?
- インクリメンタルGCなどのオプションはありますか?研究論文しか見ることができません。スループットを低くしてレイテンシを低くすることをいとわないです。
- 複数のプロセスに分割する以外に、GCサイクルを小さくするためにメモリを「パーティション化」する方法はありますか?
COntrol.Concurrent.Chan
ていますか(たとえば、使用していないように?可変オブジェクトは方程式を変更します)?まず、どのガベージを生成しているかを確認し、それをできるだけ少なくすることから始めることをお勧めします(たとえば、フュージョンが発生することを確認して、を試してください-funbox-strict
)。おそらく、ストリーミングライブラリ(iostream、パイプ、コンジット、ストリーミング)を使用して、performGC
より頻繁な間隔で直接呼び出してみてください。
MutableByteArray
、GCは、その場合のまったく関与することはありません)