為什麼將相同的數據寫入更大的預分配文件會更慢?
我正在將 4 * 4KB 塊寫入文件。
fallocate()
如果我以前使用9 個塊預分配文件,而不是只預分配 4 個塊,它始終會慢 50% 左右。為什麼?預分配 8 塊和 9 塊之間似乎有一個分界點。我還想知道為什麼第一個和第二個塊的寫入速度總是較慢。
這個測試是從我正在玩的一些文件複製程式碼中總結出來的。受這個問題
dd
的啟發,我正在使用O_DSYNC
writes 以便我可以測量磁碟寫入的真實進度。(完整的想法是開始複製一個小塊以測量最小延遲,然後自適應地增加塊大小以提高吞吐量)。我正在使用旋轉硬碟驅動器的筆記型電腦上測試 Fedora 28。它是從早期的 Fedora 升級而來的,因此文件系統不是全新的。我不認為我一直在擺弄文件系統預設值。
- 核心:4.17.19-200.fc28.x86_64
- 文件系統:ext4,在 LVM 上。
- 掛載選項:rw、relatime、seclabel
- 欄位來自
tune2fs -l
- 預設掛載選項:user_xattr acl
- 文件系統特性: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize
- 文件系統標誌:signed_directory_hash
- 塊大小:4096
- 免費塊:7866091
計時來自
strace -s3 -T test-program.py
:openat(AT_FDCWD, "out.tmp", O_WRONLY|O_CREAT|O_TRUNC|O_DSYNC|O_CLOEXEC, 0777) = 3 <0.000048> write(3, "\0\0\0"..., 4096) = 4096 <0.036378> write(3, "\0\0\0"..., 4096) = 4096 <0.033380> write(3, "\0\0\0"..., 4096) = 4096 <0.033359> write(3, "\0\0\0"..., 4096) = 4096 <0.033399> close(3) = 0 <0.000033> openat(AT_FDCWD, "out.tmp", O_WRONLY|O_CREAT|O_TRUNC|O_DSYNC|O_CLOEXEC, 0777) = 3 <0.000110> fallocate(3, 0, 0, 16384) = 0 <0.016467> fsync(3) = 0 <0.000201> write(3, "\0\0\0"..., 4096) = 4096 <0.033062> write(3, "\0\0\0"..., 4096) = 4096 <0.013806> write(3, "\0\0\0"..., 4096) = 4096 <0.008324> write(3, "\0\0\0"..., 4096) = 4096 <0.008346> close(3) = 0 <0.000025> openat(AT_FDCWD, "out.tmp", O_WRONLY|O_CREAT|O_TRUNC|O_DSYNC|O_CLOEXEC, 0777) = 3 <0.000070> fallocate(3, 0, 0, 32768) = 0 <0.019096> fsync(3) = 0 <0.000311> write(3, "\0\0\0"..., 4096) = 4096 <0.032882> write(3, "\0\0\0"..., 4096) = 4096 <0.010824> write(3, "\0\0\0"..., 4096) = 4096 <0.008188> write(3, "\0\0\0"..., 4096) = 4096 <0.008266> close(3) = 0 <0.000012> openat(AT_FDCWD, "out.tmp", O_WRONLY|O_CREAT|O_TRUNC|O_DSYNC|O_CLOEXEC, 0777) = 3 <0.000050> fallocate(3, 0, 0, 36864) = 0 <0.022417> fsync(3) = 0 <0.000260> write(3, "\0\0\0"..., 4096) = 4096 <0.032953> write(3, "\0\0\0"..., 4096) = 4096 <0.033265> write(3, "\0\0\0"..., 4096) = 4096 <0.033317> write(3, "\0\0\0"..., 4096) = 4096 <0.033237> close(3) = 0 <0.000019>
test-program.py:
#! /usr/bin/python3 import os # Required third party module, # install with "pip3 install --user fallocate". from fallocate import fallocate block = b'\0' * 4096 for alloc in [0, 4, 8, 9]: # Open file for writing, with implicit fdatasync(). fd = os.open("out.tmp", os.O_WRONLY | os.O_DSYNC | os.O_CREAT | os.O_TRUNC) # Try to pre-allocate space if alloc: fallocate(fd, 0, alloc * 4096) os.write(fd, block) os.write(fd, block) os.write(fd, block) os.write(fd, block) os.close(fd)
8 和 9 4KB 塊之間存在差異的原因是 ext4 在將由創建的未分配範圍轉換為已分配範圍時具有啟發式
fallocate()
。對於 32KB 或更小的未分配區,它只是用零填充整個區並重寫整個事物,而較大的區被分成兩個或三個較小的區並寫出。在 8 塊的情況下,整個 32KB 範圍轉換為普通範圍,前 16KB 寫入您的數據,其餘部分填零並寫出。在 9 塊的情況下,36KB 的擴展區被分割(因為它超過 32 KB),剩下 16 KB 的數據區和 20 KB 的未寫入擴展區。
嚴格來說,20KB 的未寫入範圍也應該只是零填充和寫入,但我懷疑它不會那樣做。但是,這只會稍微改變收支平衡點(在您的情況下為 16KB+32KB = 12 個塊),但不會改變基本行為。
您可以
filefrag -v out.tmp
在第一次寫入後使用來查看磁碟上的塊分配佈局。也就是說,您可以完全避免 fallocate 和 O_DSYNC 並讓文件系統完成其工作以盡快寫出數據,而不是使文件佈局比它需要的更糟……