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

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

我发现以下两种复制文件的场景存在巨大差异:

  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

编辑:测试是在 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)。

相关内容