4 * 4KB 청크를 파일에 쓰고 있습니다. 4개 블록 대신 9개 블록으로 파일을 미리 할당 하면 fallocate()
항상 약 50% 느려집니다. 왜?
사전 할당된 블록 8과 9 사이에 컷오프가 있는 것 같습니다. 또한 첫 번째 및 두 번째 블록 쓰기가 항상 느린 이유도 궁금합니다.
이 테스트는 제가 사용하고 있는 일부 파일 복사 코드에서 가져왔습니다. 에서 영감을 받다이 질문은dd
O_DSYNC
, 디스크 쓰기의 실제 진행 상황을 측정할 수 있도록 쓰기를 사용하고 있습니다 . (완전한 아이디어는 최소 대기 시간을 측정하기 위해 작은 청크 복제를 시작한 다음 적응적으로 청크 크기를 늘려 처리량을 늘리는 것입니다.)
저는 회전하는 하드 드라이브가 있는 노트북에서 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 파일 유형 need_recovery 범위 64비트 flex_bg sparse_extra 대용량 파일 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>
테스트 프로그램.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)
답변1
8개와 9개의 4KB 블록이 차이가 나는 이유는 생성된 할당되지 않은 확장 영역을 fallocate()
할당된 확장 영역으로 변환할 때 ext4에 휴리스틱이 있기 때문입니다. 32KB 이하의 할당되지 않은 익스텐트의 경우 단순히 전체 익스텐트를 0으로 채우고 전체 내용을 다시 쓰는 반면, 더 큰 익스텐트는 2~3개의 작은 익스텐트로 분할되어 기록됩니다.
8개 블록의 경우 32KB 범위 전체가 정상 범위로 변환되어 처음 16KB는 사용자 데이터로 쓰고 나머지는 0으로 채워서 씁니다. 9개 블록의 경우 36KB 범위가 분할되어(32KB를 초과하므로) 데이터용 범위는 16KB, 쓰지 않은 범위는 20KB가 남습니다.
엄밀히 말하면 20KB의 기록되지 않은 범위도 그냥 0으로 채워서 기록해야 하는데 그렇지 않은 것 같습니다. 그러나 이는 손익분기점을 약간만 변경하지만(귀하의 경우 16KB+32KB = 12블록) 기본 동작은 변경하지 않습니다.
filefrag -v out.tmp
첫 번째 쓰기 후 디스크의 블록 할당 레이아웃을 보는 데 사용할 수 있습니다 .
즉, fallocate와 O_DSYNC를 완전히 피하고 파일 레이아웃을 필요한 것보다 더 나쁘게 만드는 대신 파일 시스템이 가능한 한 빨리 데이터를 기록하도록 할 수 있습니다.
답변2
이 차이점은 흥미로워 보일 수 있지만 이해해야 할 가장 중요한 것은 이를 남용하고 있다는 것입니다 fallocate()
. fallocate()
공간은 디스크에만 예약되어 있습니다. 동기식 쓰기 성능이 향상된다는 보장은 없습니다. 즉, 디스크 검색이 필요한 파일 시스템 메타데이터 쓰기를 방지하는 것입니다.
test-program.py
를 사용하는 대신 일부 데이터 블록을 미리 작성하도록 수정하여 이를 설명 할 수 있습니다 fallocate()
. 내 ext4
파일 시스템에서는 사전 할당된 크기에 대해 더 낮은 "최소 대기 시간" 측정값을 제공합니다. 다른 파일 시스템에는 다른 성능 프로필이 있다는 점을 지적하고 싶습니다. 특히, copy-on-write 와 같은 것을 사용하여 구현된 경우에는 btrfs
작동하지 않습니다 .
코드 변경:
# Try to pre-allocate space
if alloc:
- fallocate(fd, 0, alloc * 4096)
+ os.pwrite(fd, block * alloc, 0)
+ os.fsync(fd)
결과:
openat(AT_FDCWD, "out.tmp", O_WRONLY|O_CREAT|O_TRUNC|O_DSYNC|O_CLOEXEC, 0777) = 3 <0.000088>
pwrite64(3, "\0\0\0"..., 36864, 0) = 36864 <0.035337>
fsync(3) = 0 <0.000366>
write(3, "\0\0\0"..., 4096) = 4096 <0.015217>
write(3, "\0\0\0"..., 4096) = 4096 <0.008194>
write(3, "\0\0\0"..., 4096) = 4096 <0.008371>
write(3, "\0\0\0"..., 4096) = 4096 <0.008299>
close(3) = 0 <0.000034>