我正在一个独立于操作系统的文件管理器,我正在寻找最有效的方法来复制Linux文件.Windows有一个内置函数CopyFileEx(),但是从我注意到的,Linux没有这样的标准函数.所以我想我必须实现自己的.显而易见的方法是fopen/fread/fwrite,但有更好(更快)的方法吗?我还必须能够偶尔停止每一次,以便我可以更新文件进度菜单的"复制到目前为止"计数.
Nem*_*emo 35
不幸的是,你不能sendfile()
在这里使用,因为目的地不是套接字.(名称sendfile()
来自send()
+"文件").
对于零拷贝,您可以splice()
按照@Dave的建议使用.(除非它不是零拷贝;它将是从源文件的页面缓存到目标文件的页面缓存的"一个副本".)
但是......(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);
}
Run Code Online (Sandbox Code Playgroud)
使用此循环,您将从in_fd页面缓存复制8K到CPU L1缓存,然后将其从L1缓存写入out_fd页面缓存.然后,您将使用文件中的下一个8K块覆盖L1缓存的该部分,依此类推.最终的结果是,数据中的数据buf
根本不会实际存储在主存储器中(除非可能在结束时一次); 从系统RAM的角度来看,这与使用"零拷贝"一样好splice()
.此外,它可以完美移植到任何POSIX系统.
请注意,小缓冲区是关键.对于L1数据缓存,典型的现代CPU有32K左右,因此如果使缓冲区太大,这种方法会更慢.可能更快,更慢.因此请将缓冲区保持在"几千字节"范围内.
当然,除非您的磁盘子系统非常快,否则内存带宽可能不是您的限制因素.所以我建议posix_fadvise
让内核知道你在做什么:
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
Run Code Online (Sandbox Code Playgroud)
这将向Linux内核提示其预读机制应该非常具有攻击性.
我还建议使用posix_fallocate
预分配目标文件的存储空间.这将提前告诉您是否将耗尽磁盘.对于具有现代文件系统(如XFS)的现代内核,它将有助于减少目标文件中的碎片.
我建议的最后一件事是mmap
.由于TLB颠簸,这通常是最慢的方法.(非常最近与"透明大页面"内核可能会缓解这个,我还没有最近尝试但它肯定会导致非常糟糕,所以我只会打扰测试.mmap
如果你有很多的时间基准和一个非常最近的内核.)
[更新]
评论中有一些问题是splice
从一个文件到另一个文件是否为零拷贝.Linux内核开发人员将此称为"页面窃取".手册页中的手册页splice
和注释都表示该SPLICE_F_MOVE
标志应该提供此功能.
不幸的是,支持SPLICE_F_MOVE
率在2.6.21(早在2007年)被猛烈抨击,并且从未被取代.(内核源代码中的注释永远不会更新.)如果搜索内核源代码,您会发现SPLICE_F_MOVE
实际上并未在任何地方引用.我能找到的最后一条消息(从2008年开始)说它正在"等待更换".
底线是splice
从一个文件到另一个文件调用memcpy
移动数据; 它不是零拷贝.这比使用read
/ write
使用小缓冲区的用户空间要好得多,因此您可以坚持使用标准的可移植接口.
如果"页面窃取"被添加回Linux内核,那么好处splice
会更大.(即使在今天,当目的地是一个套接字时,你会得到真正的零拷贝,使其splice
更具吸引力.)但是出于这个问题的目的,splice
不会给你买得太多.
如果您知道他们将使用 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);
Run Code Online (Sandbox Code Playgroud)
使用open
/ read
/ write
- 它们避免了fopen
和朋友一起完成的libc级缓冲.
或者,如果您使用的是GLib,则可以使用其g_copy_file
功能.
最后,可能更快,但应该测试以确保:使用open
和mmap
内存映射输入文件,然后write
从内存区域到输出文件.您可能希望保持打开/读/写作为回退,因为此方法仅限于进程的地址空间大小.
编辑:原始答案建议映射两个文件; @bdonlan在评论中提出了极好的建议,只有地图一.