概要
- TinyGate というリバースプロキシサーバの開発経緯
- epoll と io_uring による非同期I/Oの違い
- io_uring への移行によるパフォーマンス向上
- それぞれのシステムの アーキテクチャ比較
- Linux における現代的な非同期I/Oの選択肢
TinyGate開発と非同期I/Oへの関心
- 教育目的で TinyGate というシンプルなリバースプロキシサーバを開発
- workerベース の設計で動作はしたが、速度には限界
- 商用プロダクト( nginx や haproxy)に比べて性能不足
- 学生たちの要望で 非同期I/O の仕組みを徹底調査
- epoll ベースで再実装し、初期版より大幅な性能向上を実現
epollの特徴と課題
- epoll はI/Oの「準備完了」を通知
- I/Oごとに 2回のシステムコール (read/write)が必要
- 各システムコールで ユーザー・カーネルモード切替 が発生
- 多数の接続時に オーバーヘッド増大
- 長らくLinuxで標準的な非同期I/O管理方法
io_uringの登場と仕組み
- io_uring はI/Oの「完了」を通知
- アプリとカーネル間で 共有メモリ を利用
- リングバッファ で送受信を管理
- io_uring_enter() でバッチ処理が可能
- IORING_SETUP_SQPOLL でカーネルスレッドによる自動ポーリングが可能
- ただし、CPU消費増加の注意点あり
epollとio_uringのアーキテクチャ比較
- epoll :I/Oごとにシステムコール、準備通知型
- io_uring :バッチ単位でシステムコール、完了通知型
- io_uring はシステムコール回数が大幅減少
- 新しいLinuxカーネル(v5.1+) ではio_uring推奨
- 完了通知モデルへの移行でアプリケーション側の負担軽減
C言語によるサンプルコードのポイント
- epoll :インスタンス生成→FD登録→イベント発生時にread
- 合計3回のシステムコール
- io_uring :インスタンス生成→バッファ登録不要→一括送信・受信
- バッチ処理でシステムコール削減
- SQE不足やブロック時の考慮が必要
io_uringの追加機能と注意点
- ゼロコピーI/O :io_uring_register_buffers()で事前バッファ登録
- IORING_OP_SEND_ZC :カーネルへのバッファコピー自体を省略(カーネル6.0+)
- SQPOLLのCPU消費 :常時ポーリングスレッドがCPUを消費
- 一定時間アイドルでスリープ移行
- 非同期エラー処理 :cqeのresフィールドでエラー通知
まとめ:現代Linuxでの非同期I/O選択
- io_uring は現代Linuxでの非同期I/Oの新標準
- 新規プロジェクトや TinyGate の再設計にはio_uringが最適
- 古いカーネルのサポートは非推奨
- 7年以上前のカーネル を使い続けるのは避けるべき