Linux

為什麼將相同的數據寫入更大的預分配文件會更慢?

  • September 16, 2018

我正在將 4 * 4KB 塊寫入文件。fallocate()如果我以前使用9 個塊預分配文件,而不是只預分配 4 個塊,它始終會慢 50% 左右。為什麼?

預分配 8 塊和 9 塊之間似乎有一個分界點。我還想知道為什麼第一個和第二個塊的寫入速度總是較慢。

這個測試是從我正在玩的一些文件複製程式碼中總結出來的。受這個問題dd的啟發,我正在使用O_DSYNCwrites 以便我可以測量磁碟寫入的真實進度。(完整的想法是開始複製一個小塊以測量最小延遲,然後自適應地增加塊大小以提高吞吐量)。

我正在使用旋轉硬碟驅動器的筆記型電腦上測試 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 並讓文件系統完成其工作以盡快寫出數據,而不是使文件佈局比它需要的更糟……

引用自:https://unix.stackexchange.com/questions/469267