在本地复制大文件的最快方法

5

我在面试中被问到这个问题。

我说我们可以使用cp。然后被要求模仿cp的实现。

所以我想,好吧,让我们打开文件,逐个读取并将其写入另一个文件。

然后我被要求进一步优化它。我想让我们分块读取和写入这些块。但我没有一个关于什么样的块大小是好的答案。请帮帮我。

然后我被要求进一步优化。我想也许我们可以并行地从不同的线程中读取并并行地写入。但我很快意识到并行读取是可以的,但写入不会并行工作(我是指没有锁定),因为来自一个线程的数据可能会覆盖其他线程的数据。

所以我想,好吧,让我们并行读取,把它放在一个队列中,然后一个单独的线程将它从队列中取出,并逐个将其写入文件。

那样真的能提高性能吗?(我的意思是对于小文件不行,它会增加更多的开销,但对于大文件)

另外,是否有像操作系统技巧一样,可以将两个文件指向磁盘上相同的数据?我的意思是,我知道有符号链接,但除此之外呢?


1
但我很快意识到,虽然并行读取是可以的,但写入不会并行进行(我的意思是没有锁定),因为来自一个线程的数据可能会覆盖其他线程的数据。这是基于什么的?有许多方法可以从多个线程写入文件,而无需锁定。您可以使用pwrite()或多次打开文件。对于大多数文件的并行写入,真正的问题在于物理磁头需要额外的寻道。但是,如果文件系统是高端HPC文件系统,则文件可以分布在多个磁盘上,并行写入速度可以更快。 - Andrew Henle
2个回答

2
“最快的文件复制方式”将取决于系统-从存储介质到CPU。最可能的瓶颈将是存储介质,但不一定是这样。想象一下可以比您的系统更快地移动数据的高端存储,以创建物理页面映射来读取数据。

通常,移动大量数据的最快方法是尽可能少地复制它,并避免任何额外操作,特别是诸如物理磁盘头寻道之类的S-L-O-W操作。

因此,在普通单旋转磁盘工作站/台式机/笔记本电脑上进行本地复制时,最重要的是最小化物理磁盘寻道。这意味着以大块(例如1 MB)的方式进行单线程读写,以便系统可以进行任何优化,例如预读或写合并。

这可能会使您达到系统最大复制性能的95%甚至更好。即使是标准的C缓冲fopen()/fread()/fwrite()也可能获得至少80-90%的最佳性能。

你可以通过以下几种方式获得最后的一些百分比。首先,将IO块大小与文件系统块大小的倍数匹配,以便始终从文件系统读取完整的块。其次,您可以使用直接IO来绕过通过页面缓存复制数据。磁盘->用户空间或用户空间->磁盘比磁盘->页面缓存->用户空间和用户空间->页面缓存->磁盘更快,但对于单旋转磁盘副本,这并不重要,即使可以测量也不会太多。
您可以使用各种dd选项来测试像这样复制文件。尝试使用“direct”或“notrunc”。
您还可以尝试使用{{link1:sendfile()}}完全避免将数据复制到用户空间。根据实现情况,这可能比使用直接IO更快。
预先分配目标文件可能会提高复制性能,但这取决于文件系统。如果文件系统不支持稀疏文件,则将文件预分配到特定长度可能会非常缓慢。
对于从同一物理磁盘复制和复制到相同的单个旋转物理磁盘,你几乎无法做太多事情来显着提高性能 - 磁盘头会跳动,这需要时间。
SSD则容易得多 - 要获得最大的IO速率,只需通过多个线程使用并行IO即可。但是,"正常" IO的速度可能仍然达到最大速度的80-90%。
针对其他类型的存储系统(例如大型RAID阵列和/或可以将单个文件跨越多个底层存储设备进行条带化的复杂文件系统),优化IO性能变得更加有趣和复杂。在这些系统上最大化IO涉及将软件的IO模式与存储的特性匹配,这可能相当复杂。

最大化IO速率的重要部分之一是不做会显著减慢速度的事情。从物理磁盘随机读写小块数据会很容易使IO速率降至每秒几KB。如果您的写入过程将16字节块随机写入磁盘的各个位置,磁盘将花费大量时间寻找数据并在此期间移动的数据很少。

实际上,避免使用不良IO模式比在最佳情况下花费大量精力争取提高4或5个百分点更为重要。

因为如果IO是简单系统的瓶颈,那么只需购买更快的磁盘即可。


嗨,安德鲁,感谢您的详细解释。我只有一个疑问。这意味着单线程读写大块(例如1 MB),以便系统可以进行任何优化,例如预读或写合并。我们如何决定什么是好的块大小?即使按照上述逻辑,1 GB也可以工作吗? - user3732361
感谢您的所有帮助。 - user3732361

0
但我很快意识到,读取并行是可以的,但写入不会并行(没有锁定的情况下),因为来自一个线程的数据可能会覆盖其他线程的数据。
多线程通常不会加速这样的过程。您可能获得的任何性能优势都可能被同步开销抵消。
所以我想好了,让我们并行读取,将其放入队列中,然后单个线程将其从队列中取出,并逐个将其写入文件中。
这只会在支持异步I/O的系统上提供优势。
为了获得最大速度,您需要按簇因子的增量编写缓冲区大小(假设是硬文件系统)。这可以在允许排队异步I/O的系统上加速(例如,Windows)。
您还需要使用与输入文件相同的初始大小创建输出文件。这样,您的写操作永远不必扩展文件。
可能最快的文件复制方式是将输入和输出文件映射到内存中,并进行内存复制。这在将映射文件视为页面文件的系统中特别有效。

可能最快的文件复制方式是将输入和输出文件映射到内存中,然后进行内存复制。在将映射文件视为分页文件的系统中,这种方法特别有效。实际上,以这种方式使用 mmap() 很可能不是最快的方法。请阅读此 LKML 帖子:https://marc.info/?l=linux-kernel&m=95496636207616&w=2 末尾写道:“但是你的测试套件(只复制数据一次)可能对于 mmap() 来说并不理想。- Linus” - Andrew Henle
这就是为什么我有资格的原因。由于阉人文件系统的特性,我怀疑Linux上的mmap实际上并没有像硬文件系统的操作系统中的内存映射那样直接将内存映射到文件。 - user3344003

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接