Jika Anda tahu mereka akan menggunakan linux> 2.6.17, splice()
adalah cara untuk melakukan zero-copy di 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);
Sayangnya, Anda tidak dapat menggunakan sendfile()
di sini karena tujuannya bukan soket. (Nama sendfile()
berasal dari send()
+ "berkas").
Untuk zero-copy, Anda dapat menggunakan splice()
seperti yang disarankan oleh @Dave. (Kecuali itu tidak akan menjadi salinan nol; itu akan menjadi "satu salinan" dari cache halaman file sumber ke cache halaman file tujuan.)
Namun... (a) splice()
khusus untuk Linux; dan (b) Anda hampir pasti dapat melakukannya dengan baik menggunakan antarmuka portabel, asalkan Anda menggunakannya dengan benar.
Singkatnya, gunakan open()
+ read()
+ write()
dengan kecil penyangga sementara. Saya sarankan 8K. Jadi kode Anda akan terlihat seperti ini:
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);
}
Dengan loop ini, Anda akan menyalin 8K dari cache halaman in_fd ke cache CPU L1, lalu menuliskannya dari cache L1 ke cache halaman out_fd. Kemudian Anda akan menimpa bagian cache L1 itu dengan potongan 8K berikutnya dari file tersebut, dan seterusnya. Hasil akhirnya adalah data dalam buf
tidak akan pernah benar-benar disimpan di memori utama sama sekali (kecuali mungkin sekali di akhir); dari sudut pandang RAM sistem, ini sama bagusnya dengan menggunakan "zero-copy" splice()
. Plus itu sangat portabel untuk sistem POSIX apa pun.
Perhatikan bahwa buffer kecil adalah kuncinya di sini. CPU modern tipikal memiliki 32K atau lebih untuk cache data L1, jadi jika Anda membuat buffer terlalu besar, pendekatan ini akan lebih lambat. Mungkin jauh, jauh lebih lambat. Jadi pertahankan buffer dalam kisaran "beberapa kilobyte".
Tentu saja, kecuali subsistem disk Anda sangat cepat, bandwidth memori mungkin bukan faktor pembatas Anda. Jadi saya akan merekomendasikan posix_fadvise
agar kernel mengetahui apa yang Anda lakukan:
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
Ini akan memberi petunjuk kepada kernel Linux bahwa mesin baca-depannya harus sangat agresif.
Saya juga menyarankan menggunakan posix_fallocate
untuk melakukan praalokasi penyimpanan untuk file tujuan. Ini akan memberi tahu Anda sebelumnya apakah Anda akan kehabisan disk. Dan untuk kernel modern dengan sistem file modern (seperti XFS), ini akan membantu mengurangi fragmentasi pada file tujuan.
Hal terakhir yang saya rekomendasikan adalah mmap
. Ini biasanya merupakan pendekatan paling lambat dari semuanya berkat meronta-ronta TLB. (Kernel yang sangat baru dengan "hugpages transparan" mungkin mengurangi ini; saya belum mencoba baru-baru ini. Tapi itu pasti sangat buruk. Jadi saya hanya akan repot-repot menguji mmap
jika Anda punya banyak waktu untuk membandingkan dan kernel yang sangat baru.)
[Perbarui]
Ada beberapa pertanyaan di komentar tentang apakah splice
dari satu file ke file lainnya adalah zero-copy. Pengembang kernel Linux menyebutnya "pencurian halaman". Kedua halaman manual untuk splice
dan komentar di sumber kernel mengatakan bahwa SPLICE_F_MOVE
bendera harus menyediakan fungsi ini.
Sayangnya, dukungan untuk SPLICE_F_MOVE
dicabut pada 2.6.21 (pada tahun 2007) dan tidak pernah diganti. (Komentar di sumber kernel tidak pernah diperbarui.) Jika Anda mencari di sumber kernel, Anda akan menemukan SPLICE_F_MOVE
sebenarnya tidak dirujuk di mana pun. Pesan terakhir yang dapat saya temukan (dari 2008) mengatakan "menunggu pengganti".
Intinya adalah splice
itu dari satu file ke file lain memanggil memcpy
untuk memindahkan data; itu tidak salinan nol. Ini tidak jauh lebih baik daripada yang dapat Anda lakukan di ruang pengguna menggunakan read
/write
dengan buffer kecil, jadi sebaiknya Anda tetap menggunakan antarmuka portabel standar.
Jika "pencurian halaman" pernah ditambahkan kembali ke kernel Linux, maka keuntungan dari splice
akan jauh lebih besar. (Dan bahkan hari ini, ketika tujuannya adalah soket, Anda mendapatkan salinan nol yang sebenarnya, membuat splice
lebih menarik.) Tapi untuk tujuan pertanyaan ini, splice
tidak banyak membelikanmu.