今流行りの Ring Buffer とその罠

石 立行 (@ishitatsuyuki)

@ishitatsuyuki

東京大学 理学部情報科学科 3年

低レイヤーでどうでも良さそうな問題を
一生懸命考えるのが好きです

 

今日もどうでも良さそうな問題を見ていきます

Ring Buffer は、 First-In First-Out の Queue を実現するデータ構造

今回は、Single-Producer Single-Consumerのユースケースに着目します

(1スレッドが書き込み、1スレッドが読み出し)

近年、Spectre の Mitigation で

コンテキストスイッチのコストが爆上がりしている

アイデア: Consumer (e.g. Userland) と

Producer (e.g. Kernel) を

別々のスレッドにすれば、

コンテキストを同時に2つ持てる

Ring Buffer のコンテキストスイッチへの応用

  • virtio (2008)
    • Guest Kernel Host Kernel Host Guest と必要だった遷移を、Ring Buffer により回避
  • io_uring (2019)
    • syscall を場合によってはコンテキストスイッチ不要に
  • perf ring buffer, bpf ringbuf
    • Kernel からのトレースデータを高速に Userspace に渡す

よくある Ring Buffer の実装

void push(int val) {
    int write_ = atomic_load(write, relaxed);
    // handle buffer full...
    data[write_ % LEN] = val;
    atomic_store(write, write_ + 1, release);
}

void pop(int *out) {
    int read_ = atomic_load(read, relaxed);
    // handle buffer empty
    int write_ = atomic_load(write, acquire);
    if (read_ == write_) blocking_wait();
    *out = data[read_ % LEN];
    atomic_store(read, read_ + 1, release);
}

Acquire-Release anomaly

なぜ、Anomaly が起こるのか

x86 では原則として、メモリ操作は順番通りに実行されることが保証される

Read-after-Write

RAW を禁止すると、強力なハードウェア最適化である Store Forwarding が行えない

自スレッドで Write が実行されたように見えても、他スレッドからは見えているとは限らない

例外:

事情:
 

結果:

解決策1

スリープしない

他スレッドの書き込みは、スピンしていれば「いつか」読み出せる

問題: 不要なリソース消費

解決策2

SeqCst

relaxed, acquire を seq_cst に置き換え

C++標準では total order を保証する

→ Write が lock xchg になり、完了してから次の Read が行われる

問題: 30倍も遅くなる

300Mops/s → 10Mops/s

解決策3

OS にどうにかしてもらう

プロセッサ間割り込みを使って、他 CPU の

実行を中断して書き込みを吐き出させる

 

Linux: membarrier()

Windows: FlushProcessWriteBuffers()

問題: Not Portable

一応内部実装を仮定して同じ機能を実現する術はある

参考

今流行りの Ring Buffer とその罠

By Tatsuyuki Ishi

今流行りの Ring Buffer とその罠

  • 29