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

io_uringはmmapよりも高速です

概要

  • ディスクから直接データを取得 する方が、 メモリキャッシュ より高速な場合があるという実験的証拠
  • CPU性能の伸び悩みストレージ帯域の急成長 が背景
  • 従来のキャッシュ戦略 が必ずしも最適ではない現状
  • ベンチマーク による詳細なパフォーマンス比較
  • io_uring など新しいI/O技術の活用が鍵

ディスク直読みは本当にメモリキャッシュより速いのか?

  • 従来の常識 :「未使用メモリはディスクキャッシュに使うのが最適」というComputer Scienceのドグマ
  • 最新のハードウェア事情 :ディスク(特にSSD)の帯域幅は 指数関数的に増加、一方でメモリアクセス遅延は ほぼ停滞
  • 仮説 :ディスクから直接データを読む方が、メモリキャッシュを使うよりも高速な場合がある

実験環境

  • CPU :AMD EPYC 7551P 32-Core
  • メモリ :DDR4 2133MHz 96GB
  • ストレージ :Samsung PM983a PCIe 3.0 SSD(1.92TB×2、RAID0構成)
  • メモリ帯域(理論値) :シングルスレッドで約13GB/s
  • SSD帯域(理論値) :合計6.2GB/s
  • データセット :50GB

ベンチマーク内容

  • 乱数整数(0〜20)をバッファに書き込み、「10」の個数をカウント
  • mmap()による単純ループ での実装
    • 初回(ディスクから読み込み):0.61GB/s
    • 2回目(ページキャッシュ利用):3.71GB/s
  • perfによる解析 :シングルスレッドかつ非ベクトル命令でCPU性能がボトルネック

ループのアンローリングとベクトル化

  • ループを16回分アンローリング し、ベクトル命令(AVX2など)を利用
  • パフォーマンス :5.51GB/s(ページキャッシュ利用時)
    • 理論値13GB/sには届かないが、元のコードより50%高速化
  • CPUのベクトル命令最適化 が重要

SSD直読みの限界とio_uringの活用

  • mmap()によるデフォルトのI/O はカーネルのページフォールト処理がボトルネック
  • SSDの理論帯域を活かしきれない (0.61GB/sはSATA SSDよりは速いが、NVMeのポテンシャルの1割程度)
  • 本気で高速I/Oを実現するには、ユーザ空間でI/Oパイプラインを組む必要
  • io_uringベースのI/Oエンジン を自作し、6ワーカー・キュー深度8192・16KBバッファで最大速度を狙う

まとめ:新しいI/O戦略が必要な時代

  • CPUの進化が鈍化 する一方で、 ストレージ帯域の伸び が著しい
  • 従来の「キャッシュ優先」戦略 ではハードウェア性能を活かしきれないケースが増加
  • io_uring などの新技術を活用し、 I/Oパイプラインを最適化 する必要
  • 「ディスクは遅い、メモリは速い」という常識の再考 が求められる
  • 今後のシステム設計 では、「何をキャッシュするか」よりも「どのようにI/Oを組むか」が重要なテーマ

この内容は、 現代的なハードウェア環境 における I/O最適化 の新しい指針を示しています。 io_uringベクトル化 などの技術を駆使し、 システム全体のボトルネック を見極めて設計することが、今後ますます重要となるでしょう。

Hackerたちの意見

これって「io_uringはmmapより速い」ってタイトルにすべきじゃない?でも、そうするとあんまり反応は得られなさそうだね!それはさておき、面白い記事と実験だね。

笑。ありがとう!

それを使おう。HNのガイドラインには「誤解を招くかリンクベイトでない限り、元のタイトルを使ってください」と書いてあるから、その「ただし」条項がここで当てはまるみたいで、上のタイトルを変更したよ。ありがとう!もし誰かもっと良いタイトル(つまり、より正確で中立的なもの)を提案してくれたら、また変更できるよ。

いや、「io_uringがmmapより速い」ってのはちょっとした常識みたいなもんだよね。連続したページフォルトは、慎重に調整された非同期I/Oより遅いから。この記事のポイントは、NVMeフラッシュみたいなPCIeデバイスから直接読み込む方が、まずRAMにキャッシュするよりも速い場合があるってこと。

mmapの巨大ページオプションとも比較すべきじゃない?私の理解では、まさにこの状況のために設計されてるんだよね。それなしだと公平な比較とは言えないと思う。失礼だけど、タイトルがちょっと釣りっぽく感じるな。どちらの方法も最終的にはメモリからデータを読み出してるだけで、ただ異なるI/Oメソッドを使ってるだけだし。

元のブログのタイトルは意図的に釣りっぽくしてるんだよね。人をクリックさせるために。あと、みんなに本当に考えてほしいって思ってる。キャッシュされたファイルデータにすぐアクセスできるかどうかが実験のポイントだから。mmap()で巨大ページのファイルを開けないんだよね。void* buffer = mmap(NULL, size_bytes, PROT_READ, (MAP_HUGETLB | MAP_HUGE_1GB), fd, 0);はうまくいかない。私のコードはここにあるよ https://github.com/bitflux-ai/blog_notes。何かアイデアある?

PCIeの帯域幅がメモリの帯域幅より高いからって、これはおかしいよ。PCIe 5.0のx16スロットは最大64GB/sを提供するからね。それは完全に飽和してるし、かなり古いXeonサーバーでも、NUMAノードごとに100GB/s以上のメモリ読み出しが問題なくできるよ。新しいHBM対応のXeon Max 9480なんかは、HBM(最大64GB)で1.6TB/sを超えることもできるし、DDR5は300GB/s以上に達することもある。すべてのPCIeレーン(デュアルソケットXeon 6で196レーン)を飽和させても、理論上は最大で約784GB/sが得られるけど、これはそういうCPUの最大メモリ帯域幅(12チャネル×8,800 MT/s = 合計105,600 MT/s、つまり約784GB/s)と一致するんだ。つまり、ソリッドステートIOはかなり近づいてきてるけど、非連続アクセスパターンではそんなに速くないよね。多くのワークロードをSSDに移行できると思うけど、まだ微妙なところがあるね。

たくさんではないけど、DDR5のチャネル帯域幅とPCIeのレーンを合計すると、ほとんどのシステムではPCIeの帯域幅が高いよ。そうだね。HBMとL3キャッシュはPCIeよりも高くなるだろうね。

え、今はPCIeの帯域幅がメモリの帯域幅より高いの?それはすごい、いつそんなことになったの?全然追いついてなかった。i9-14900kを見たばかりだけど、確かにそうだね。ただし、すべてのPCIeレーンを合計した場合だけど。他にももっとそういうチップがあるんだろうな。すごい!

サーバーチップについては、ちょっとおかしいよね。5世代のEpycは128レーンのPCIEx5を持ってて、1TB/s以上のPCIe帯域幅があるんだ(12チャネルのDDR5で6400のRAM帯域幅が600GB/sに対して)。

「いいえ。」DDR5-8000はチャネルごとに64GB/sだよ。デスクトップCPUは2チャネル持ってる。PCI-E 5.0のx16は64GB/s。デスクトップは1つのx16しかない。

ちょっと待って、今はPCIeの帯域幅がメモリの帯域幅より高いの?うーん。誰かRDIMMスロット付きのPCIeカードを作ってくれ。

これがCXLの約束(または要件?)だね。メモリを中央で管理して、サーバーがPCIe経由でアクセスするっていう。https://en.wikipedia.org/wiki/Compute_Express_Link 実際にCXLを使ってるところがどれくらいあるのか気になるな。今のところ、導入してる顧客の話は聞いたことがないよ。

MADV_SEQUENTIALでmadvise()呼び出しがないの?

それは間違ってるよ。mmapコードがページフォルトでストールされてるから(データがメモリにあってもプロセスにマップされてないときのソフトページフォルトも含む)。io_uringのコードは、バックグラウンドで6スレッドで全てのフェッチ作業をして、その後に完了したバッファをカウンターに渡してるみたい。最初に各ページの最初のバイトを読み取って、そのページセクションをカウンターに渡す6スレッドで同じことをやってみれば、似たようなパフォーマンスが得られるよ。mmapの動作を制御するためにmadviceや巨大ページも使えるしね。

全体の投稿を要約したみたいだね。それがポイントなんだ。「mmap」は遅いのは直列処理だから。

もしかしたら誤解してるかもしれないけど、読んでみると「io_uringはmmapより速い」っていうより「8つのSSDでのraid0は3チャネルのDRAMよりスループットが高い」って感じに思える。

タイトルが間違って編集されてるね。元のページタイトルは「メモリは遅く、ディスクは速い」で、あなたが言ってることをそのまま言ってるよ:NVMe RAIDはRAMよりも多くの帯域幅を提供できるんだ。

本当の違いは、io_uringとO_DIRECTを使うと自分でキャッシュを管理しなきゃいけない(他のプロセスと共有できないし、メモリが圧迫されてるとOSが自動でキャッシュを回収できない)けど、mmapだとOSがそれを管理してくれるってこと。もしLinuxに「io_uringから受け取ったこのバッファをVFSページキャッシュみたいに管理して、他のプロセスとも共有できるようにして、戻したい時はこのコールバックを呼んでくれればいいよ」っていうAPIがあったら、io_uringは本当にmmapを置き換えられると思う。今のLinuxにはPSIがあって、必要に応じてOSがメモリを回収できるけど、バッファの共有には役立たないんだよね。

試してみるべきこと:MADV_SEQUENTIAL、hugetlb、バックグラウンドプリフェッチスレッド。

これ、すごくいいね。数週間前にperf_eventsを使って注釈付きの逆アセンブルを見る方法を学んだばかりだけど、まだそこに何があるのかを解釈するのは難しい。ここで指摘されてるmmap()の遅さは、例えばRAMにあるページをもう少し積極的にマッピングすることで、ある程度は改善できると思う。だから、来年mmapが再びio_uringより速くなるって言われても驚かないかも(まあ、専門家じゃないからその点は注意だけど)。