当前位置: 首页 > 面试题库 >

在Linux中复制文件的最有效方法

仲孙昊焱
2023-03-14
问题内容

我在与OS无关的文件管理器中工作,并且正在寻找为Linux复制文件的最有效方法。Windows有一个内置函数CopyFileEx(),但是据我所知,Linux没有这种标准函数。所以我想我将必须实现自己的。明显的方法是fopen
/ fread / fwrite,但是是否有更好(更快)的方法呢?我还必须有能力每隔一段时间停止一次,以便可以更新文件进度菜单的“到目前为止”。


问题答案:

不幸的是,您不能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);
}

通过此循环,您将把8k从in_fd页面缓存复制到CPU
L1缓存中,然后将其从L1缓存写入out_fd页面缓存中。然后,您将用文件中的下一个8K块覆盖L1缓存的该部分,依此类推。最终结果是,输入的数据buf根本不会真正存储在主存储器中(最后可能一次除外);从系统RAM的角度来看,这与使用“
zero-copy”一样好splice()。另外,它非常适合任何POSIX系统。

请注意,此处的小缓冲区是关键。典型的现代CPU的L1数据高速缓存大约为32K,因此,如果将缓冲区设置得太大,这种方法会比较慢。可能慢得多。因此,将缓冲区保持在“几千字节”范围内。

当然,除非您的磁盘子系统非常快,否则内存带宽可能不是您的限制因素。因此,我建议posix_fadvise让内核知道您要做什么:

posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);

这将向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并不能为您带来多少好处。



 类似资料:
  • 问题内容: 我正在处理的项目(node.js)暗示了文件系统的许多操作(复制/读取/写入等)。我想知道哪些方法是最快的,我很乐意得到建议。谢谢。 问题答案: 这是使用流在一行代码中复制文件的好方法: 在节点v8.5.0中,添加了copyFile

  • 我正在处理的项目(node.js)意味着对文件系统的大量操作(复制、读取、写入等)。 哪些方法最快?

  • 问题内容: 我需要从超过2GB的文件中更新最后一行,这些文件由无法读取的文本行组成。目前,它可以通过逐行循环工作。但是,我想知道是否有任何编译的库可以更有效地实现这一目标?谢谢! 目前的方法 问题答案: 更新:使用ShadowRanger的答案。它更短且更健壮。 对于后代: 读取文件的最后N个字节,然后向后搜索换行符。

  • 问题内容: 将File写入javax.servlet.ServletOutputStream的最有效方法是什么? 编辑: 如果使用NIO会更有效吗? 问题答案: 哪里是的FileInputStream和是。 IOUtils是Apache Commons中 Commons IO 模块的实用程序。

  • 我搜索一个好的方法来复制一个文件(二进制或文本)。我写了几个样本,每个人都工作。但我想听听经验丰富的程序员的意见。 我错过了好的例子,并搜索了一种与C++一起工作的方法。 ANSI-C方式 POSIX方法(K&R在“C编程语言”中使用该方法,更低级别) Kiss-C++-StreamBuffer-Way 复制-算法-C++-方法 Own-Buffer-C++-Way LINUX-WAY//需要ke

  • 问题内容: 有没有简单/快速的方法可以在Go中复制文件? 我找不到文档中的快速方法,并且搜索互联网也无济于事。 问题答案: 警告:此答案主要是关于向文件添加第二个链接,而不是复制内容。 一个 强大的 和 高效率 副本的概念很简单,但并不容易实现,因为需要处理一些边缘情况和系统限制由目标操作系统强加的,它的配置。 如果您只想复制现有文件,则可以使用。这样可以避免在应用程序中移动字节并节省磁盘空间。对