Linux> 2.6.17、splice()
を使用することがわかっている場合 Linux でゼロコピーを行う方法です:
//using some default parameters for clarity below. Don't do this in production.
#define splice(a, b, c) splice(a, 0, b, 0, c, 0)
int p[2];
pipe(p);
int out = open(OUTFILE, O_WRONLY);
int in = open(INFILE, O_RDONLY)
while(splice(p[0], out, splice(in, p[1], 4096))>0);
残念ながら、sendfile()
は使用できません ここでは、宛先がソケットではないためです。 (名前 sendfile()
send()
から来ています + "ファイル").
ゼロコピーの場合、splice()
を使用できます @Daveの提案どおり。 (ただし、ゼロ コピーではなく、ソース ファイルのページ キャッシュから宛先ファイルのページ キャッシュへの「1 つのコピー」になります。)
しかし... (a) splice()
Linux 固有です。 (b) ポータブル インターフェースを正しく使用すれば、ほぼ確実に同じように使用できます。
つまり、open()
を使用します。 + read()
+ write()
小さい 一時バッファ。 8Kをお勧めします。したがって、コードは次のようになります:
int in_fd = open("source", O_RDONLY);
assert(in_fd >= 0);
int out_fd = open("dest", O_WRONLY);
assert(out_fd >= 0);
char buf[8192];
while (1) {
ssize_t read_result = read(in_fd, &buf[0], sizeof(buf));
if (!read_result) break;
assert(read_result > 0);
ssize_t write_result = write(out_fd, &buf[0], read_result);
assert(write_result == read_result);
}
このループでは、in_fd ページ キャッシュから CPU L1 キャッシュに 8K をコピーし、L1 キャッシュから out_fd ページ キャッシュに書き込みます。次に、L1 キャッシュのその部分をファイルの次の 8K チャンクで上書きします。最終的な結果は、buf
のデータは 実際にメイン メモリに格納されることはありません (おそらく最後に 1 回を除く)。システム RAM の観点からは、これは「ゼロコピー」 splice()
を使用するのと同じくらい良いです .さらに、どの POSIX システムにも完全に移植可能です。
ここでは小さなバッファが重要であることに注意してください。典型的な最近の CPU は、L1 データ キャッシュ用に 32K 程度を持っているため、バッファを大きくしすぎると、このアプローチは遅くなります。おそらく、はるかに遅くなります。したがって、バッファは「数キロバイト」の範囲に収めてください。
もちろん、ディスク サブシステムが非常に高速でない限り、メモリ帯域幅はおそらく制限要因ではありません。 posix_fadvise
をお勧めします あなたが何をしようとしているのかをカーネルに知らせるために:
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
これにより、先読み機構が非常に積極的であるべきだというヒントが Linux カーネルに与えられます。
posix_fallocate
を使用することもお勧めします 宛先ファイルのストレージを事前に割り当てます。これにより、ディスクが不足するかどうかが事前にわかります。また、最新のファイル システム (XFS など) を備えた最新のカーネルの場合、宛先ファイルの断片化を減らすのに役立ちます。
最後にお勧めするのは mmap
です .これは通常、TLB スラッシングのおかげで、最も遅いアプローチです。 (「transparent hugepages」を備えたごく最近のカーネルでは、これが軽減される可能性があります。最近試したことはありません。しかし、以前は非常に悪かったことは確かです。そのため、わざわざ mmap
をテストするだけです。 ベンチマークに十分な時間があり、最新のカーネルを使用している場合。)
[更新]
splice
かどうかについてのコメントにいくつかの質問があります あるファイルから別のファイルへのゼロコピーです。 Linux カーネル開発者は、これを「ページ盗用」と呼んでいます。 splice
の両方の man ページ カーネルソースのコメントによると、SPLICE_F_MOVE
flag はこの機能を提供する必要があります。
残念ながら、SPLICE_F_MOVE
のサポートは 2.6.21 (2007 年) で削除され、置き換えられることはありませんでした。 (カーネル ソースのコメントは更新されていません。) カーネル ソースを検索すると、 SPLICE_F_MOVE
が見つかります。 実際にはどこにも参照されていません。私が見つけた最後のメッセージ (2008 年以降) には、「交換を待っています」と書かれています。
結論は splice
です あるファイルから別のファイルへの呼び出し memcpy
データを移動します。 そうではない ゼロコピー。これは、 read
を使用してユーザー空間で実行できるよりもはるかに優れているわけではありません /write
バッファが小さいので、標準の移植可能なインターフェースに固執することもできます。
「ページ盗用」が Linux カーネルに再び追加された場合、splice
の利点は はるかに大きいでしょう。 (そして今日でも、宛先がソケットの場合、真のゼロコピーが得られ、splice
になります。 より魅力的です。)しかし、この質問の目的のために、splice
あまり買わない。