推測より計測。どうも、かわしんです。
昨日、Recall.ai のリングバッファがどのように設計されたのかを考察しました。
その後、Hacker News のコメントを見ているとリングバッファはオーバーエンジニアリングでもっと簡単な方法として以下の手法などが提案されていました。
WebSockets cost us $1M on our AWS bill | Hacker News
TCP のウィンドウサイズを変えたとしても、ユーザー空間とカーネル空間の無駄なメモリコピーの量は変わらないので今回のメモリコピーのボトルネックには効かない思われます。
/dev/shm については、Memory backed file にデータを書き込んでそのファイルのファイルディスクリプタを Unix ドメインソケットで送り、コンシューマ側で mmap すればメモリコピーの回数は共有メモリのリングバッファの手法と同じなので、一考の価値はあります。
しかし、フレームごとに tmpfs にファイルを作成して Unix ドメインソケット経由で送信して mmap しなおすオーバーヘッドがある上に、書き込み時にはメモリページの割り当てが発生し、読み込み時には mmap された領域のページテーブルを埋めるまでページフォルトが大量に発生するなど、オーバーヘッドが大きそうで本当にパフォーマンスが良いのかどうかは怪しいです。
/dev/shm を直接使う場合はプロデューサが死んだ時に大きめのメモリがリークするので回収するための死活監視が必要になりますが、memfd_create(2) を使えば自動的に回収されます。
Mojo については、Rust のバインディングはまだ完成していないので使えないと思いますが、mojo::BigBuffer は実質的には上の /dev/shm の手法と同じです。
ということで、リングバッファと Memory backed file を Unix ドメインソケットで送って mmap する手法のどちらがどのくらい速いのかが気になったので検証してみました。
実際の検証コードは文末に埋め込んでありますが、特徴としては
- 3MB の固定長のランダムなデータを送信する
- プロデューサは 3 スレッドから並列にそれぞれ 100 フレームずつ書き込み、コンシューマはシングルスレッドで読み込む
- リングバッファの最大数と Unix ドメインソケットで同時に送れるのは最大 30 フレーム分まで
- 同一プロセス内の別のスレッドからそれぞれ送るが、パフォーマンス特性としては別プロセスの場合と変わらないはず
- Unix ドメインソケットを使ったスロットリングは複数コネクションが必要になるのでとりあえずセマフォで代替
- memfd は Linux にしかなくて、macOS で開発できなかったので、Linux と macOS の両方で使える
tempfileクレートで代替。実態は Memory backed file なのでパフォーマンス特性は同じはず
結果
- Linux
- リングバッファ: 1.95 s くらい
- Memory backed file: 5.2 s くらい
- macOS
- リングバッファ: 750 ms くらい
- Memory backed file: 10 s くらい
リングバッファの方が結構速いです。macOS では Memory backed file の方が遅すぎるので何か別の問題がありそうな気もします。
Linux (AWS EC2)
リングバッファ
$ for i in {1..5}; do ./cmp_ringbuffer/target/release/cmp_ringbuffer; done
producer 2
producer 1
producer 0
start
producer 0 finished
producer 1 finished
producer 2 finished
finished: 1.973099031s
producer 2
producer 1
producer 0
start
producer 2 finished
producer 1 finished
producer 0 finished
finished: 1.964035006s
producer 2
producer 1
producer 0
start
producer 2 finished
producer 0 finished
producer 1 finished
finished: 1.956276696s
producer 2
producer 1
producer 0
start
producer 2 finished
producer 0 finished
producer 1 finished
finished: 1.968726078s
producer 2
producer 1
producer 0
start
producer 2 finished
producer 1 finished
producer 0 finished
finished: 1.959291665s
Memory backed file
$ for i in {1..5}; do ./cmp_memfd/target/release/cmp_memfd; done
producer 2
producer 1
producer 0
start
producer 1 finished
producer 2 finished
producer 0 finished
finished: 5.206459836s
producer 2
producer 1
producer 0
start
producer 1 finished
producer 2 finished
producer 0 finished
finished: 5.225634239s
producer 2
producer 1
producer 0
start
producer 1 finished
producer 2 finished
producer 0 finished
finished: 5.207194755s
producer 2
producer 1
producer 0
start
producer 0 finished
producer 1 finished
producer 2 finished
finished: 5.216124744s
producer 2
producer 1
producer 0
start
producer 2 finished
producer 1 finished
producer 0 finished
finished: 5.194187799s
macOS (手元の Macbook Pro 2018)
リングバッファ
$ for i in {1..5}; do ./cmp_ringbuffer/target/release/cmp_ringbuffer; done
producer 0
producer 1
producer 2
start
producer 2 finished
producer 1 finished
producer 0 finished
finished: 717.866646ms
producer 0
producer 2
producer 1
start
producer 0 finished
producer 2 finished
producer 1 finished
finished: 733.328122ms
producer 1
producer 0
producer 2
start
producer 0 finished
producer 1 finished
producer 2 finished
finished: 736.151382ms
producer 1
producer 2
producer 0
start
producer 0 finished
producer 2 finished
producer 1 finished
finished: 740.951105ms
producer 0
producer 2
producer 1
start
producer 2 finished
producer 1 finished
producer 0 finished
finished: 751.015461ms
Memory backed file
$ for i in {1..5}; do ./cmp_memfd/target/release/cmp_memfd; done
producer 0
producer 1
producer 2
start
producer 1 finished
producer 0 finished
producer 2 finished
finished: 10.866407023s
producer 0
producer 2
producer 1
start
producer 0 finished
producer 1 finished
producer 2 finished
finished: 11.167087375s
producer 2
producer 1
producer 0
start
producer 1 finished
producer 2 finished
producer 0 finished
finished: 10.824447892s
producer 0
producer 2
producer 1
start
producer 1 finished
producer 0 finished
producer 2 finished
finished: 10.833614174s
producer 0
producer 2
producer 1
start
producer 1 finished
producer 0 finished
producer 2 finished
finished: 11.07388444s