1.従来のデータ転送
Socketネットワークからの従来のデータ転送には、4つのデータコピーと4つのコンテキストスイッチが必要です。
- ディスクファイルをオペレーティングシステムのカーネルバッファに読み込みます。
- カーネルバッファ内のデータをユーザースペースバッファにコピーします。
- データは、ユーザースペースバッファーからカーネルのソケットネットワーク送信バッファーにコピーされます。
- データは、カーネルのソケットネットワーク送信バッファからネットワークカードインターフェース(ハードウェア)のバッファにコピーされ、ネットワーク送信はネットワークカードによって実行されます。
従来の方法では、ディスクファイルを読み取り、ネットワーク、4つのデータコピー、および4つのコンテキストスイッチを介して送信するのは非常に面倒です。実際のIOの読み取りと書き込みにはIO割り込みが必要であり、CPUは割り込みに応答する必要があります(コンテキストスイッチングをもたらします)。後で
DMA
CPUの割り込み要求を引き継ぐために導入されましたが、4つのコピーにはまだ不要なリンクがあります。
2.ゼロコピー実装の原則
ゼロコピーの目的は、IOプロセスでの不要なコピーを減らし、ユーザープロセスのアドレス空間とカーネルアドレス空間の間のコンテキスト切り替えによって引き起こされるオーバーヘッドを減らすことです。仮想マシンはカーネルを直接操作できないため、その実装にはオペレーティングシステムOSのサポートが必要です。つまり、APIを公開するにはカーネルカーネルが必要です。
2.1Nettyでのゼロコピー
Direct Buffers
:Nettyの送受信ByteBufferは、ダイレクトバッファ(Direct Buffer
)を使用してゼロコピーを実現し、メモリ領域に直接スペースを割り当てて、データの読み取りと書き込みのセカンダリメモリコピーを回避します。これにより、ソケットの読み取りと書き込みのゼロコピーが実現されます。
従来のヒープバッファがソケットの読み取りと書き込みに使用される場合、JVMはヒープバッファをダイレクトメモリにコピーしてから、ソケットに書き込みます。オフヒープダイレクトメモリと比較して、メッセージには送信プロセス中にもう1つのバッファメモリコピーがあります。
CompositeByteBuf
:複数のByteBufをByteBufにカプセル化し、統合されたカプセル化されたByteBufインターフェイスを提供できます。CompositeByteBufは、実際には複数のバッファーを結合しませんが、それらの参照を保存するため、データコピーを回避し、ゼロコピーを実現します。
従来のByteBufferでは、2つのByteBufferのデータを組み合わせる必要がある場合、size = size1 + size2の新しい配列を作成してから、2つの配列のデータを新しい配列にコピーする必要があります。ただし、Nettyが提供するByteBufの組み合わせを使用すると、このような操作を回避できます。
- Nettyのファイル転送クラスは、メソッド
DefaultFileRegion
を呼び出すことによってFileChannel.transferTo()
ゼロコピーを実装し、ファイルバッファ内のデータはターゲットチャネルに直接送信されます。最下層はLinuxオペレーティングシステムのsendfile()
実装を呼び出します。データはDMAエンジンによってファイルからカーネル読み取りバッファーにコピーされます。DMAはデータをカーネル読み取りバッファーからネットワークカードインターフェイス(ハードウェア)のバッファーにコピーします。ネットワークカードはネットワーク送信を実行します。
率直に言って、Javaのメモリには、ヒープメモリ、スタックメモリ、文字列定数プールなどが含まれます。その中で、ヒープメモリは最大のメモリであり、Javaオブジェクトが格納される場所でもあります。一般に、データが必要な場合IOからのものであるヒープメモリを読み取るには、中央のソケットバッファを通過する必要があります。つまり、データの一部が2回コピーされて最後に到達します。データの量が多いと、不要になります。 nettyがデータを受信する必要がある場合、その時点で、彼はヒープメモリの外部にあるメモリを開き、データはIOからそのメモリに直接読み取られます。nettyでは、これらのデータは次のようになります。 ByteBufを介して直接操作されるため、送信が高速化されます。
2.2Javaでのゼロコピー
-
FileChannel.transferTo()
ゼロコピーはJavaメソッドによって実現されます。最下層はLinuxオペレーティングシステムを呼び出すsendfile()
ことによって実装されます。データはDMAエンジンによってファイルからカーネル読み取りバッファーにコピーされます。DMAはデータをカーネル読み取りバッファーからネットワークカードインターフェイス(ハードウェア)バッファーにコピーします。ネットワーク送信はネットワークカードによって実行されます。 -
FileChannel.map()
ゼロコピーはJavaメソッドによって実現されます。最下層はLinuxオペレーティングシステムを呼び出すことmmap()
によって実装されます。アドレスマッピングはカーネルバッファのメモリとユーザーバッファのメモリの間で行われます。このメソッドは大きなファイルの読み取りに適しています。ファイルの内容は変更されますが、後でSocketChannel
送信する場合は、CPUがデータをコピーする必要があります。