不幸的是,你不能在这里使用sendfile(),因为目的地不是套接字。 (名称sendfile()来自send()“file”)。
对于零拷贝,您可以使用@Dave建议的splice()。 (除了它不会是零拷贝;它将是从源文件的页面缓存到目标文件的页面缓存的“一个副本”。)
但是…(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 result = read(in_fd, &buf[0], sizeof(buf));
if (!result) break;
assert(result > 0);
assert(write(out_fd, &buf[0], result) == result);
}
使用此循环,您将从in_fd页面缓存中复制8K到CPU L1缓存,然后将其从L1缓存写入out_fd页面缓存。那么你将覆盖L1缓存的那部分与文件中的下一个8K块,依此类推。最终的结果是,buf中的数据根本不会实际存储在主内存中(除了最后一次)从系统RAM的角度来看,这与使用“零拷贝”拼接()一样好。此外,它完全可携带到任何POSIX系统。
请注意,这里的小缓冲区是关键。典型的现代CPU对于L1数据缓存有32K左右,所以如果使缓冲区太大,这种方法会变慢。可能要慢很多。所以保持缓冲区在“几千字节”范围内。
当然,除非您的磁盘子系统非常快,否则内存带宽可能不是您的限制因素。所以我会建议posix_fadvise让内核知道你的目标:
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
这将给Linux内核提供一个提示,它的预读机器应该是非常积极的。
我也建议使用posix_fallocate预先分配目标文件的存储。这将提前告诉你是否会用尽磁盘。而对于具有现代文件系统(如XFS)的现代内核,它将有助于减少目标文件中的碎片。
最后我会推荐的是mmap。这通常是最慢的方法,这要归功于TLB的颠簸。 (非常近的内核带有“透明的hugepages”可能会减轻这个;我最近还没有尝试过,但是它肯定是非常糟糕的,所以如果你有很多时间进行基准测试和一个最近的内核,我只会打扰测试mmap。
[更新]
关于从一个文件到另一个文件的拼接是否为零拷贝的评论中有一些问题。 Linux内核开发人员称此为“窃取”页面。拼接的手册页和comments in the kernel source都说SPLICE_F_MOVE标志应该提供此功能。
不幸的是,对SPLICE_F_MOVE的支持是yanked in 2.6.21 (back in 2007),从未被替换。 (内核源代码中的注释从来没有更新过。)如果您搜索内核源代码,您将发现SPLICE_F_MOVE在任何地方都没有被实际引用。 last message I can find(从2008年)说,它正在“等待更换”。
底线是从一个文件到另一个文件的拼接,调用memcpy来移动数据;它不是零拷贝。这不如用户空间中使用具有小缓冲区的读/写操作更好,因此您可以遵守标准的便携式界面。
如果“页面窃取”被添加回到Linux内核中,则拼接的好处将会更大。 (甚至今天,当目的地是一个套接字时,你会得到真正的零拷贝,使拼接更有吸引力)。但是,为了这个问题的目的,拼接不会非常买你的。