世界を動かす技術を、日本語で。

LinuxにおけるEpollとio_uringの比較

2026年6月21日原文(sibexi.co)

概要

  • TinyGate というリバースプロキシサーバの開発経緯
  • epollio_uring による非同期I/Oの違い
  • io_uring への移行によるパフォーマンス向上
  • それぞれのシステムの アーキテクチャ比較
  • Linux における現代的な非同期I/Oの選択肢

TinyGate開発と非同期I/Oへの関心

  • 教育目的で TinyGate というシンプルなリバースプロキシサーバを開発
  • workerベース の設計で動作はしたが、速度には限界
  • 商用プロダクト( nginxhaproxy)に比べて性能不足
  • 学生たちの要望で 非同期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年以上前のカーネル を使い続けるのは避けるべき

Hackerたちの意見

C++と非同期ネットワーキングが好きなら、Boost asioを使ってみて!

データベースサーバーでasioのepollバックエンドをio_uringに切り替えたら、CPUの使用率が急上昇したよ。多分、使い方やイベントコードへの統合の仕方によるんだろうね。

Boostは本当に不便だよね。巨大な動的ライブラリで、ビルドして使うのが面倒くさい。CMakeを使っている時ですら、Boostを見つけられるようにインストールするのが超面倒だった。(私、Mac使ってたけどね)

https://github.com/concurrencykit/ck と https://github.com/microsoft/mimalloc を見てみて。ゼロコピーでメモリ整列されたリバースプロキシにぴったりだよ。それに、DDoS対策やもっと高度なL4のことを考えてるなら、https://docs.ebpf.io/ebpf-library/libxdp/libxdp/もチェックしてみて。

そうだね、計画は他のレベルで最適化を適用してから、アロケーターに進むことだった。今、生徒たちと一緒にアロケーターを勉強しているところで、前のブログの投稿はZig言語のカスタムアロケーターについてだったよ。

でも、私の生徒たちは私ほど喜んでいなかった - 彼らは本当に役立つものを作りたかったから、私たちの「製品」が強いアーキテクチャの制限があって、nginxやhaproxyのような巨人に勝てなかったことに本当に失望していた。GitHubのリポジトリをちょっと見たけど[1]、CPUピンニングをやってる様子はなさそうだね。スレッドをCPUピンニングして、リッスンソケットもCPUピンニングすれば、もう少しパフォーマンスを引き出せると思うよ(sockopt SO_INCOMING_CPU)。もし送信ソケットもCPUアラインメントを取れば、かなりのブーストが得られるはずだけど、私の知る限り、そういうAPIはあまり良くないんだ。Linuxには互換性のあるNIC用のAPI(トラフィックスティアリング/フロースティアリング)があって、それが機能することもあるけど、自分のNICがどんなハッシュを使っているか(多分トペリッツだね)を知って、バックエンドへのソースポート選択を管理すれば、適切にハッシュされるポートを選べるよ。目標は、プロキシがクロスCPU通信なしでパケットを処理できるようにすることだね。[1] https://github.com/sibexico/TinyGate

ちょっと引き出す

基本的に、リポジトリのv0とv1は全く違う実装で、ほぼゼロから書かれているんだ。今は3つ目の実装に取り組んでいて、これが最後のものになると思うよ。:) 完全に異なるアーキテクチャの選択がなされている。

そのパッチのベンチマークを見てみたいな。

2050年だね。Linuxでソケットをポーリングする方法が20種類もあるんだ。

そう、io_uringでもね。io_uringはシングルショットからマルチショットに進化して、さらに速くなるんだ。

io_uringベースのウェブサーバーの共有バッファはまだ試してないけど、ファイルから読み込んで書き込む代わりに、mmapした領域から直接送信してるからなんだ。実際、io_uringでsendfileを使いたいんだけど、まだサポートされてないんだよね。俺の書いた記事には、RustやkTLSみたいな流行りの言葉も入ってるよ: https://blog.habets.se/2025/04/io-uring-ktls-and-rust-for-ze... それ、HNにも載ったよ: https://news.ycombinator.com/item?id=44980865

Hacker Newsで議論の続きを見る