ゼロコピー(Zero-Copy) I/Oとカーネルバイパス
1. 概要
ゼロコピー(Zero-Copy)とは、データをある記憶媒体から別の媒体へ転送する際、特にカーネル空間(kernel space)とユーザ空間(user space)の間でCPUが直接バイトを複写する冗長コピー(redundant copy)を除去または最小化し、データが発生源から目的地までCPUの介在なしに流れるよう設計するI/O最適化技法である。「コピーをなくす」というより「CPUによるメモリ間コピーをなくす」という意味であり、ディスク・ネットワーク装置のDMAコピーはそのまま維持される。
ゼロコピーが必要になった背景は、ハードウェア性能の非対称な発展にある。ネットワークは1GbEから100GbE・400GbEへ、ストレージはSATA HDDからNVMe SSDへと帯域幅が数十~数百倍に増加したが、メモリコピー速度(メモリ帯域)とCPUクロックはそれほど速くならなかった。その結果、「データをどこから読みどこへ送るか」よりも「読んだデータをCPUが何回複写し直すか」がスループットのボトルネックとして浮上した。100GbE回線を埋めるには毎秒約12.5GBを転送する必要があるが、従来経路のように同じデータをCPUで二回コピーすると、メモリバスにはその数倍のトラフィックがかかり、CPUコア一つがコピーだけで飽和してしまう。
ゼロコピーという用語はやや誤解を招くが、ディスクやネットワークカードがメモリを出入りするDMA転送は物理的に必ず必要であり、これは除去対象ではない。ゼロコピーがなくそうとするのは、あくまで同一ホスト内部でアドレス空間が分離されているという理由だけで発生するCPUコピーである。したがって「コピーがゼロ」という表現は「CPUによる冗長コピーがゼロ」と読むのが正確であり、この区別を明確にしてこそ、各技法が何を除去するのかを混同なく理解できる。
ゼロコピーが狙うコストは三つある。第一はCPUサイクルの浪費で、コピーはCPUが行う作業の中で最も非生産的なものである。第二はメモリ帯域の消費で、コピー一回は読み込み+書き込みで帯域を二倍消費する。第三はCPUキャッシュ汚染(cache pollution)で、大容量データをコピーすると、本来アプリケーションロジックが使うべきキャッシュラインが追い出され全体性能が落ちる。ここにユーザ/カーネルモード切替(context switch)のコストが加わる。ゼロコピーはこの四つを同時に減らすことを目標とし、Apache Kafka・Nginx・Netflix Open Connectのような大容量転送システムの性能を支える中核原理として定着している。
2. 従来のI/O経路のコスト構造
ゼロコピーの価値を理解するには、まず従来経路がなぜ高コストかを見る必要がある。サーバがディスクのファイルを読んでソケットへ送り出す最も単純なコード(read()のあとwrite())を考えると、データは下図のように四回コピーされ、ユーザ/カーネルモード切替が四回起きる。
flowchart TB
subgraph US["ユーザ空間(User Space)"]
APP["アプリケーションプロセス"]
UB["ユーザバッファ(user buffer)"]
end
subgraph KS["カーネル空間(Kernel Space)"]
PC["ページキャッシュ(read buffer)"]
SB["ソケットバッファ(socket buffer)"]
end
DISK[("ディスク")]
NIC["NIC(ネットワークカード)"]
DISK -->|"①DMAコピー"| PC
PC -->|"②CPUコピー"| UB
UB -->|"③CPUコピー"| SB
SB -->|"④DMAコピー"| NIC
APP -.->|"read() 呼び出し"| PC
APP -.->|"write() 呼び出し"| SB
第一のコピー(①)はディスクコントローラのDMA(Direct Memory Access)がファイル内容をカーネルのページキャッシュ(read buffer)へ移すもので、CPUは関与しない。第二のコピー(②)が問題の核心で、read()システムコールが返る際にカーネルバッファのデータをユーザ空間バッファへCPUが直接コピーする。アプリケーションがデータをまったく加工せずそのまま送るとしても、アドレス空間が分離されているという理由だけでこのコピーが強制される。第三のコピー(③)はwrite()が再びユーザバッファのデータをカーネルのソケットバッファへCPUコピーするもので、最後(④)でようやくNICのDMAがソケットバッファをネットワークへ送り出す。
結局、データは一度だけ転送すればよいものを合計四回コピー(DMA二回、CPU二回)し、そのうちCPUが行う②・③が純粋な浪費である。モード切替もread()で二回(進入・復帰)、write()で二回の合計四回発生する。モード切替自体のコストはコピーより小さいが、切替のたびにTLB・分岐予測器・キャッシュ状態が撹乱され間接コストが累積する。
この浪費の深刻さはデータが大きくなるほど線形に増大する。たとえば1GBのファイルを転送すると、CPUコピー二回だけで2GBを読み2GBを書き、メモリバスに4GBのトラフィックが追加で発生し、このコピーが終わるまで該当CPUコアは他の要求を処理できない。同時接続が数千に達するウェブ・ストリーミングサーバでは、このコピーコストが累積し、ネットワーク・ディスクには余裕があるのにCPU飽和でスループットが限界に達するという典型的なボトルネックを生む。ゼロコピーはまさにこの「見えないCPU・メモリ税」を取り除く技法である。下表は以後説明する技法との比較のためにコピー・切替回数を整理したものである。数値は比較の補助であり、「なぜ減るのか」は本文の原理で理解すべきである。
| 方式 | 総コピー | CPUコピー | モード切替 | 備考 |
|---|---|---|---|---|
| read + write | 4 | 2 | 4 | 従来経路 |
| mmap + write | 3 | 1 | 4 | ユーザバッファコピー除去 |
| sendfile(基本) | 3 | 1 | 2 | カーネル内部で完結 |
| sendfile + SG-DMA | 2 | 0 | 2 | CPUコピー完全除去 |
| splice | 2 | 0 | 2 | パイプベースの参照移動 |
この表から読み取るべき核心は、「コピー回数が減る方向」と「CPUコピーがゼロへ収束する方向」が性能改善の二つの軸だという点である。従来経路のCPUコピー二回を一回へ、さらにゼロへ落とすことがゼロコピー技法の共通目標である。またモード切替四回を二回へ減らすことも副次的だが重要な改善であり、sendfileのように一つのシステムコールで全過程をカーネルに委ねる設計がこれを可能にする。
3. ゼロコピーの実装技法
ゼロコピーは一つの技術ではなく、コピーとモード切替を段階的に除去する複数技法の集合である。以下ではコピーを一段ずつ減らしていく順で、オペレーティングシステムが提供する代表的手段を原理中心に見ていく。各技法は「何を除去し、その代償にどんな制約・コストを負うか」という観点で比較すると差が明確になる。
A. mmap + write
mmap()はファイルをプロセスの仮想アドレス空間に直接マッピング(memory-map)し、カーネルのページキャッシュとユーザ空間が同じ物理ページを共有するようにする。したがって「カーネルバッファ→ユーザバッファ」のCPUコピー(②)が消える。アプリケーションはマッピングされたアドレスを読むだけでページキャッシュのデータをそのまま見ることになる。ただしその後write()でソケットへ送る際、「マッピング領域→ソケットバッファ」のCPUコピー(③)は依然として残り、合計三回コピー(DMA 2 + CPU 1)となる。
mmap方式の落とし穴は、コピー削減の代償として生じる複雑さである。転送中に別のプロセスが同じファイルを切り詰め(truncate)たり変更したりすると、マッピング領域へアクセスする際にSIGBUSシグナルが発生しプロセスが異常終了しうる。またマッピング・解除(mmap/munmap)時にページテーブルを更新しTLBを無効化するコストがかかり、小さなファイルを頻繁にマッピングするとこのコストがコピー削減分を侵食する。したがってmmapゼロコピーはファイルのライフサイクルを制御できる状況で慎重に使うべきで、少量・頻繁なI/Oよりも大容量ファイルを繰り返し転送する場合に有利である。
B. sendfile
sendfile()はファイルディスクリプタからソケットディスクリプタへデータをカーネル内部で直接転送するシステムコールである。データがユーザ空間へ上がってこないため②・③のコピーがいずれも除去されるように見えるが、基本実装ではページキャッシュのデータをソケットバッファへ移すCPUコピーが一回残り、合計三回コピーとなる。しかしモード切替は一度のシステムコールで終わり二回に減り、何よりデータがユーザ空間を経由しないためキャッシュ汚染とアドレス空間切替が消える。Nginxのsendfile on;ディレクティブがまさにこのシステムコールを使う。
sendfileの制約は、データをそのまま通過させる場合にのみ使える点である。転送前に内容を加工したり条件に応じて一部だけ送ったりする必要があれば、データをユーザ空間へ引き上げねばならずsendfileの利点が消える。また出発地がページキャッシュに載せられるファイルでなければならないため、パイプや別のソケットから来たデータには使えない。こうした限界を越えるために登場したのが下記のsplice系である。
C. sendfile + scatter-gather DMA(真のゼロコピー)
最後のCPUコピーまでなくすには、NICがスキャッタ・ギャザDMA(scatter-gather DMA)、すなわち散在した複数のメモリ断片を一つのパケットに集めて転送する能力をもつ必要がある。Linuxカーネル2.4でソケットバッファディスクリプタの構造を改良し、ソケットバッファには実データの代わりにページキャッシュの位置(アドレス)と長さ情報(ディスクリプタ)だけを載せ、NICがDMAでページキャッシュから直接データを収集するようにした。これによりCPUコピーは完全にゼロになり、残るコピーはDMA二回(ディスク→ページキャッシュ、ページキャッシュ→NIC)だけである。これがよく言われる「真のゼロコピー」である。
flowchart LR
APP["アプリケーション(sendfile 呼び出し)"]
subgraph KERNEL["カーネル空間(Kernel Space)"]
PC["ページキャッシュ(page cache)"]
DESC["ソケットバッファディスクリプタ(位置・長さのみ)"]
end
DISK[("ディスク")]
NIC["NIC(scatter-gather DMA)"]
DISK -->|"①DMAコピー"| PC
PC -->|"②ディスクリプタのみ伝達(データコピーなし)"| DESC
DESC -->|"③SG-DMAがページキャッシュから直接収集"| NIC
PC -.->|"データはページキャッシュに常駐"| NIC
APP -->|"ファイル fd → ソケット fd"| KERNEL
D. splice / vmsplice と送信側 MSG_ZEROCOPY
splice()はカーネル内部のパイプ(pipe)バッファを媒介に、二つのファイルディスクリプタの間でデータの参照(ページポインタ)を移動させ、任意のfd組合せ(ファイル↔ファイル、ファイル↔ソケット等)でコピーなしの転送を可能にする。パイプバッファが実データではなくページへの参照だけを保持するため、sendfileが要求していた「出発地がファイルでなければならない」という制約から外れ、ソケット対ソケットの中継のようなプロキシパターンにもゼロコピーを適用できる。一方、アプリケーションが自ら作ったデータをコピーなしで送りたい送信側では、MSG_ZEROCOPYフラグ(SO_ZEROCOPYソケットオプション)を使う。この場合カーネルはユーザバッファの物理ページを固定(pin)したうえでそのページを直接NICへDMAし、転送が終われば完了キューへ非同期通知する。コピーはないがページ固定・通知処理という新たなオーバーヘッドが生じるため、パケットが大きい大容量転送でのみ利得が大きく、少量転送ではかえって損になりうる。「コピーをなくした場所に別の管理コストが入り込む」というトレードオフを常に念頭に置くべきである。
4. 受信側ゼロコピーとカーネルバイパス:RDMA・DPDK・io_uring
前述の技法は主に送信(ディスク→ネットワーク)経路を扱うが、受信経路や超高性能領域では、そもそもカーネルネットワークスタック自体を迂回するカーネルバイパス(kernel bypass)が使われる。三つの代表的アプローチを比較すると、その差と実務的含意が明確になる。
RDMA(Remote Direct Memory Access)は、一つのノードのNICがネットワークの向こうの別ノードのアプリケーションメモリへ直接読み書きを行う技術で、InfiniBand・RoCE(RDMA over Converged Ethernet)・iWARPで実装される。両端ともカーネルとCPUがデータ経路から外れるため「エンドツーエンドのゼロコピー」が成立する。RDMAは単なるコピー除去を超え、リモートCPUの介在なしに相手メモリを直接読み書きするワンサイド(one-sided)演算と、ユーザ空間からNICキューへ直接命令を載せるカーネル迂回転送をあわせて提供する点が特徴である。その結果、遅延はマイクロ秒単位まで下がりCPU使用率はほぼゼロへ収束する。HPCやAI学習クラスタが400Gb/s級のInfiniBandで数百枚のGPUを接続し集合通信(all-reduce)を行えるのは、RDMAのゼロコピーのおかげである。ただしNICが接続状態とフロー制御をハードウェアで担うため、障害時のデバッグが難しく、機能変更がハードウェアに縛られ柔軟性が落ちる(ossification)というコストを払う。
DPDK(Data Plane Development Kit)はNICをユーザ空間に直接マッピングし、割り込みの代わりにポーリングモードドライバ(PMD)でパケットを取り込み、ヒュージページ(hugepage)でTLBミスを減らしてカーネルスタックを丸ごと迂回する。通信装置・ファイアウォール・L4ロードバランサのように毎秒数千万個のパケットを処理せねばならない領域で事実上の標準である。その代わりCPUコア一つをポーリングに常時占有(busy-poll)し、電力とコアを犠牲にする。
DPDKが割り込みの代わりにポーリングを使う理由も、ゼロコピーと同じ文脈の「オーバーヘッド除去」である。毎秒数千万のパケットが押し寄せると、パケットごとに割り込みをかけるコスト(コンテキスト切替・キャッシュ撹乱)が処理コストを超える割り込み嵐(interrupt storm)が発生する。DPDKはコア一つを捧げてNICキューを絶え間なく覗くことでこのコストを丸ごとなくし、コピー除去と結合して汎用カーネルスタックでは不可能なパケットレートを達成する。電力・コアを犠牲にする代わりに遅延の決定性(determinism)まで得られる点が、通信装置領域で好まれる理由である。
io_uringはLinuxの非同期I/Oインターフェースで、ユーザとカーネルが共有する投入キュー(SQ)・完了キュー(CQ)リングバッファを通じてシステムコール回数自体を劇的に減らす。あらかじめ登録したバッファ(registered buffer)と結合すればコピーとモード切替をあわせて抑制でき、近年は受信ゼロコピー(zero-copy receive, zcrx)機能が追加され、TCPペイロードをユーザメモリへ直接受け取る道が開けた。RDMA・DPDKがカーネルスタックを完全に捨てるのに比べ、io_uringゼロコピーRXはカーネルTCPスタックをそのまま使いながらコピーだけをなくす点が実務的に重要である。標準TCP互換性とデバッグの容易さを保ったままゼロコピーの利得を得られるからである。
三つの方式は受信側でコピーをなくすアプローチという共通点があるが、その代償に手放すものが互いに異なる。下表で性格を整理すると選択基準が明確になる。
| アプローチ | カーネルスタック | 主な用途 | 主なコスト |
|---|---|---|---|
| RDMA | 迂回 | HPC・AI学習ファブリック | ハードウェア依存・デバッグ難度 |
| DPDK | 迂回 | 通信装置・ファイアウォール・LB | コア常時占有・電力 |
| io_uring zcrx | 維持 | 汎用高性能サーバ | 比較的新しい・カーネルバージョン依存 |
三つの方式の差が生じる根本的理由は、「カーネルスタックをどこまで手放すか」の選択にある。RDMA・DPDKは性能のためにカーネルの汎用性・安全性・可観測性を手放し、io_uringはそれを守りながらコピーだけを減らそうとする。したがって汎用サーバアプリケーションはsendfile・io_uring系を、専用ネットワークアプライアンスやAI学習ファブリックはDPDK・RDMAを選ぶのが一般的な設計判断である。
この選択は運用組織の能力とも直結する。DPDK・RDMAはポーリングコア管理、ヒュージページ設定、NICファームウェア・ドライバのバージョン整合、輻輳制御(RoCEのPFC・ECN)のような低レベルチューニングを要し専任人員が必要となる。一方、sendfile・io_uring経路は標準カーネルスタックの上で動作し、既存の運用・可観測性ツールをそのまま使える。結局ゼロコピー方式の選択は純粋な性能数値だけでなく「どれだけの複雑さを負えるか」という組織的判断を含み、この点が技術士水準の設計意思決定において特に重要である。
5. 深化:実務適用事例と最新動向
ゼロコピーは理論にとどまらず、大規模サービスの性能を実際に左右してきた。代表的事例と最近の流れを整理する。
第一に、Apache Kafkaはブローカがディスクのログセグメントをコンシューマへ転送する際、JavaのFileChannel.transferTo()(内部的にsendfile)を使い、メッセージをユーザ空間へ引き上げずページキャッシュからソケットへ直接流す。メッセージが加工なしにそのまま消費されるKafkaの特性とゼロコピーが噛み合い、少ないCPUでも毎秒数百万メッセージのスループットを達成する。これは「アプリケーションがデータに触れないときゼロコピーの利得が最大化される」という原理を示す教科書的事例である。
ここで注目すべき細部は、Kafkaがデータをディスクではなくオペレーティングシステムのページキャッシュから読んで送る点である。最近生成されたメッセージはまだページキャッシュに熱い状態で残っており、コンシューマがこれを消費する際ディスクI/Oなしにキャッシュ→NICへそのまま流れる。ゼロコピーとページキャッシュが結合し「ディスクベースのキューでありながらメモリキューに近い遅延」を出すのであり、これはゼロコピーが単独技法ではなくオペレーティングシステムのメモリ管理とともに設計されるとき威力を発揮することを示す。
第二に、Netflix Open Connect CDNサーバはFreeBSDの非同期sendfileで一台から数十~数百Gb/sの動画を提供する。問題はTLS暗号化がデータをユーザ空間へ引き上げてゼロコピーを壊す点であったが、これをkTLS(カーネルTLS)で解決した。暗号化をカーネル(またはNICオフロード)で行い、暗号化トラフィックでもデータがユーザ空間を経由しないゼロコピーを維持したのである。セキュリティとゼロコピーが衝突する際、暗号化地点をカーネルへ下ろして両者を両立させた設計であり、現代のセキュア転送でゼロコピーを守る標準パターンとなった。
第三に、AIデータセンターとGPUDirectである。大規模モデル学習ではNICやNVMe SSDがGPUメモリへ直接DMAするGPUDirect RDMA・GPUDirect Storageが中核であり、これは「データがCPUメモリを経由しない」というゼロコピーの概念をGPU領域へ拡張したものである。CPUとシステムメモリを経由しないためGPUのデータ飢餓(starvation)を減らし、学習パイプラインのボトルネックを緩和する。
第四に、io_uringゼロコピーネットワーキングの成熟である。Meta主導で提案されたio_uringベースの受信ゼロコピーはヘッダ分離(header splitting)とフローステアリング(flow steering)でペイロードだけをユーザメモリへ直接受け取るようにし、2024年のKernel Recipesの発表では小さな転送サイズでも効率的でバニラTCPと互換な方向へ発展した。カーネルバイパスの性能とカーネルスタックの汎用性の間の長年の緊張を狭めようとする試みとして注目される。
この四つの事例を貫く流れは、ゼロコピーが「送信・ファイル転送」という狭い領域から出発し、受信経路、暗号化区間、GPUメモリ、非同期I/O全般へ拡張しているという点である。データ移動コストが演算コストを圧倒するAI・大容量ストリーミングの時代に、ゼロコピーは特定のシステムコールの問題ではなく「データが発生源から消費地までCPUを経由せず流れるようにする」というアーキテクチャ設計原則へと昇格しつつある。
6. 考慮事項および示唆点
技術士の観点からゼロコピーを導入・設計する際は、次を総合的に判断すべきである。
適用戦略 — 「データに触れないか」が前提:ゼロコピーの最大の利得はアプリケーションがデータを変形せずそのまま中継するときに生じる。ファイルサーバ、CDN、メッセージブローカ、プロキシのような通過(pass-through)性格のワークロードが第一候補である。逆に圧縮・暗号化・フィルタリング等のデータ変換が必要なら、その処理地点をカーネル(kTLS)・NICオフロード・GPUへ下ろしてゼロコピー区間を保てるかをまず検討すべきである。
トレードオフ — コピーをなくした場所に生じるコスト:ゼロコピーはただではない。ページ固定(pinning)、非同期完了通知、mmapのSIGBUSリスク、DPDKのコア常時占有、RDMAのデバッグ難度・ハードウェア依存など、新たな管理コストと複雑さが伴う。特に少量・頻繁なI/Oでは管理オーバーヘッドがコピー削減を上回りかえって遅くなりうるため、転送単位が十分に大きいかをベンチマークで確認してから適用すべきである。
展望 — カーネルバイパスと汎用性の収束:かつては極限性能のためにカーネルを捨てるか(DPDK・RDMA)、カーネル内にとどまるか(sendfile)の二者択一だったが、io_uringゼロコピーRX・AF_XDP・kTLSは「カーネルスタックの安全性・可観測性を守りながらコピーだけをなくす」方向へ収束している。新規システムを設計するなら、ハードウェア依存の大きい全面バイパスよりも、標準スタックと共存するゼロコピー経路を優先検討するのが保守性・移植性の面で有利である。
関連技術との整合:ゼロコピーは単独技術ではなく、DMA・ページキャッシュ・仮想メモリ(mmap)・NUMA・ヒュージページ、そして転送層のTLS・TCPオフロードとともに理解すべきである。特にNUMA環境ではデータ・NIC・CPUが同じノードにあってこそゼロコピーの利得が十全に出るため、割り込み親和性(IRQ affinity)とメモリ配置まであわせて調整すべきである。可観測性の面ではコピーが消えるため従来のCPUプロファイリングだけではボトルネックが表れないことがあり、メモリ帯域・DMA・NICキュー深度のような指標をあわせて監視すべきである。
セキュリティ・安定性の考慮:ユーザバッファをカーネル・NICが直接アクセスする構造は、性能と引き換えの攻撃面拡大を意味する。ページ固定中のバッファ変更、DMA領域境界の検証、RDMAのリモートメモリアクセス権限の統制などを厳密に設計しなければデータ完全性・隔離が壊れうる。性能最適化がセキュリティモデルを弱めないよう、IOMMUのようなハードウェア保護機構とともに適用すべきである。
参考資料
- IBM Developer, "Efficient data transfer through zero copy." https://developer.ibm.com/articles/j-zerocopy/
- Linux Journal, "Zero Copy I: User-Mode Perspective." https://www.linuxjournal.com/article/6345
- The Linux Kernel Documentation, "MSG_ZEROCOPY." https://www.kernel.org/doc/html/latest/networking/msg_zerocopy.html
- Kernel Recipes 2024, "Efficient zero-copy networking using io_uring." https://kernel-recipes.org/en/2024/schedule/efficient-zero-copy-networking-using-io_uring/
- NVIDIA, "GPUDirect RDMA." https://docs.nvidia.com/cuda/gpudirect-rdma/
一言まとめ: ゼロコピーはカーネル・ユーザ空間の間のCPUコピーを除去してCPU・メモリ帯域・キャッシュを節約するI/O最適化であり、mmap・sendfile・SG-DMA・spliceからRDMA・DPDK・io_uringのカーネルバイパスまでつながり、Kafka・Netflix・AIデータセンターの大容量転送性能を支えつつ、「データに触れないとき」に最大の効果を出すトレードオフベースの技術である。