我发现以下两种复制文件的场景存在巨大差异:
- 将文件复制到新文件中。
- 复制现有文件中的文件并覆盖它。
我预计这两个操作需要相同的时间才能完成。但实际上第一种情况要快得多。我在两个不同的文件系统上尝试了这种方法,得到了相同的结果。知道为什么第一种情况要快得多吗?
Linux 中的示例:
$ dd bs=1024 count=1000000 if=/dev/zero of=dummyfile.txt
1000000+0 records in
1000000+0 records out
1024000000 bytes (1.0 GB) copied, 7.45639 s, 137 MB/s
$ while [ "1" == "1" ]; do time cp dummyfile.txt dummyfile2.txt ; rm dummyfile2.txt ; done
real 0m0.850s
user 0m0.003s
sys 0m0.847s
real 0m0.778s
user 0m0.000s
sys 0m0.776s
real 0m0.775s
user 0m0.004s
sys 0m0.772s
real 0m0.775s
user 0m0.003s
sys 0m0.770s
real 0m0.776s
user 0m0.008s
sys 0m0.766s
^C
$ rm dummyfile2.txt -f
$ while [ "1" == "1" ]; do time cp dummyfile.txt dummyfile2.txt ; done
real 0m0.839s
user 0m0.003s
sys 0m0.834s
real 0m6.056s
user 0m0.005s
sys 0m1.683s
real 0m6.614s
user 0m0.002s
sys 0m1.405s
real 0m6.858s
user 0m0.003s
sys 0m1.436s
编辑:测试是在 SSD 磁盘上进行的。我在 HDD 上观察到了相同的趋势,但差距低于 SDD(2-3 倍)。以下页面解释了为什么 SSD 在覆盖方面比 HDD 慢得多:
答案1
实际上物理复制需要相同的时间。但是,如果在现有 i 节点上执行操作,ext4 文件系统驱动程序 close() 会在数据真正写入之前等待,如果在新节点上执行写入操作,则不会等待写入操作。我做了一些实验,意识到这是 ext4 的功能。我还没有看到在 btrfs、zfs、ext3、ext 上复制有如此大的差异。
我怎么能说关闭是耗时操作呢?斯特拉斯提供信息:
$ strace -tt -T cp bigfile newfile
...
14:36:41.985437 open("bigfile", O_RDONLY) = 3 <0.000009>
14:36:41.985466 fstat(3, {st_mode=S_IFREG|0664, st_size=647608649, ...}) = 0 <0.000007>
14:36:41.985495 open("newfile", O_WRONLY|O_CREAT|O_EXCL, 0664) = 4 <0.000086>
14:36:41.985602 fstat(4, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0 <0.000007>
14:36:41.985633 fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0 <0.000008>
... a lot of reads and writes
14:36:43.584223 close(4) = 0 <0.000009>
14:36:43.584248 close(3) = 0 <0.000008>
...
$ strace -tt -T cp bigfile existingfile
...
14:36:52.393034 open("bigfile", O_RDONLY) = 3 <0.000010>
14:36:52.393071 fstat(3, {st_mode=S_IFREG|0664, st_size=647608649, ...}) = 0 <0.000009>
14:36:52.393104 open("existingfile", O_WRONLY|O_TRUNC) = 4 <0.097058>
14:36:52.490211 fstat(4, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0 <0.000007>
14:36:52.490278 fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0 <0.000009>
... a lot of reads and writes
14:36:54.047408 close(4) = 0 <5.346015>
14:36:59.393466 close(3) = 0 <0.000011>
...
请注意,close(4) 需要 5 秒以上的时间,而在复制到现有文件的情况下,它会在创建新文件时立即完成。
我跑iostat检查系统正在做什么。在测试之前执行了 bigfile 的读取,以避免文件系统缓存的影响。
# iostat sda 1 100
Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
sda 0.00 0.00 0.00 0 0
sda 0.00 0.00 0.00 0 0
--------------- copy to a new file starts here -----------------------
sda 24.00 0.00 8340.00 0 8340
sda 174.00 8.00 86596.00 8 86596
sda 170.00 0.00 86020.00 0 86020
--------------- copy to a new file finishes here ---------------------
sda 177.00 4.00 90112.00 4 90112
sda 176.00 4.00 89600.00 4 89600
sda 166.00 0.00 84992.00 0 84992
sda 161.00 4.00 81920.00 4 81920
sda 157.00 0.00 78888.00 0 78888
sda 52.00 0.00 26224.00 0 26224
sda 0.00 0.00 0.00 0 0
sda 0.00 0.00 0.00 0 0
sda 0.00 0.00 0.00 0 0
--------------- copy to the existing file starts here ----------------
sda 12.00 0.00 4128.00 0 4128
sda 172.00 4.00 87040.00 4 87040
sda 180.00 4.00 91648.00 4 91648
sda 175.00 0.00 89600.00 0 89600
sda 173.00 4.00 88064.00 4 88064
sda 168.00 4.00 83532.00 4 83532
sda 159.00 0.00 81408.00 0 81408
sda 181.00 4.00 92160.00 4 92160
sda 30.00 0.00 14960.00 0 14960
--------------- copy to the existing file finishes here --------------
sda 0.00 0.00 0.00 0 0
sda 3.00 0.00 28.00 0 28
请注意,虽然 cp 是从用户的角度完成的,但在写入数据之前,复制到新文件就已经完成,并且写入操作仍会继续。
答案2
我相信是 ext4 的auto_da_alloc
行为造成了这种差异。
参见 ext4 内核文档
@Zaboj Campula 的回答告诉我们,close()截断的文件将阻塞,直到写入的数据被“强制写入磁盘”。ext4 自动检测到这种替换截断的使用模式。
同样,如果您将新创建的文件重命名(mv)为某个现有文件,您会发现 rename() 系统调用会阻塞一段时间。这就是替换重命名使用模式。
答案3
覆盖需要进行寻道操作...
必须读取文件的权限才能知道是否可以覆盖它(这可能是也可能不是问题,具体取决于您的文件系统)。此外,必须截断该文件。
这需要驱动器寻找文件所在的位置。
复制到新文件会生成新的权限并使用最近可用的空闲块进行写入,无需寻道。
研究不执行此操作的“写时复制”文件系统(例如 ZFS)。