写入新文件与覆盖:性能问题

las*_*gar 5 linux ssd hard-drive

我观察到以下两种复制文件的场景之间存在巨大差异:

  1. 将文件复制到新文件中。
  2. 将文件复制到现有文件中并覆盖它。

我预计这两个操作需要相同的时间才能完成。但实际上第一种情况要快得多。我在两个不同的文件系统上尝试了这个,得到了相同的结果。知道为什么第一个场景要快得多吗?

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
Run Code Online (Sandbox Code Playgroud)

编辑:测试是在 SSD 磁盘上执行的。我在 HDD 上观察到相同的趋势,但差距低于 SDD (2-3 倍)。下页解释了为什么 SSD 的覆盖速度比 HDD 慢得多:

修剪(计算)维基百科页面

Zab*_*ula 6

实际上物理复制也需要同样的时间。但是,当在现有 i 节点上完成操作时,ext4 文件系统驱动程序 close() 会在数据真正写入之前等待,如果在新节点上完成操作,则不会等待写入操作。我做了一些实验,发现这是ext4的特性。我还没有看到在 btrfs、zfs、ext3、ext 上复制有这样的区别。

怎么说close是个耗时的操作呢?strace提供信息:

$ 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>
...
Run Code Online (Sandbox Code Playgroud)

请注意,close(4) 需要超过 5 秒的时间,而在复制到现有文件的情况下,它会在创建新文件时立即执行。

我运行iostat来检查系统在做什么。在测试之前执行大文件的读取以避免文件系统缓存效应。

# 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
Run Code Online (Sandbox Code Playgroud)

请注意,复制到新文件在写入数据之前完成,并且写入操作会继续一些,尽管 cp 是从用户角度完成的。