btrfs透明压缩值得尝试
之前我们写过btrfs快照的使用,不得不说,btrfs是一个非常先进的文件系统(至少在设计理念上是的)。
除了快照,btrfs的另外一个高级特性是透明压缩,强调透明是由于压缩的过程由文件系统完成,而用户以及应用无感。这就是接口抽象的意义。
透明压缩有诸多好处,引用Arch wiki:
Btrfs 支持透明自动压缩,这不仅能缩减文件体积,还能通过减弱写入放大效应显著延长闪存介质的使用寿命。
在某些情况下(例如单线程繁重文件 I/O 时),压缩可以提升性能,但在其他场景中(如多线程或高 CPU 占用并涉及大量文件读写的任务),性能则会明显下降。
通常采用压缩算法越快(zstd 和 lzo)性能越好,一些基准测试提供了详细的性能对比数据。
简而言之,优点是省地方,对SSD友好,缺点是会降低一定的吞吐量,以及会占用CPU资源,可是在SSD速度已经很充足,CPU性能总是过剩的现在,这点缺点算不了什么。
所以很值得尝试。
确实很值得“尝试”
btrfs给用户提供了zlib(类似zip),lzo,zstd三个可选算法。
对于压缩级别,zlib:1~9;zstd:-15~-1、1~15,需为整数。
所以理论上有25种尝试组合(忽略zstd的负值,lzo无法设置等级)
手动尝试耗时耗力还不准确,所以我万能的Gemini又下场了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| #!/bin/zsh set -e
trap 'umount -q /mnt; rmmod brd' INT TERM
# 依赖检查 check_dependency() { if ! command -v $1 &> /dev/null; then echo "错误: 未找到必需的工具 $1,请先安装" exit 1 fi }
check_dependency zsh check_dependency hyperfine check_dependency compsize check_dependency jq check_dependency bc check_dependency mkfs.btrfs check_dependency mkfs.ext4
# 基准测试数据目录 BENCHMARK_DIR="/tmp/silesia/"
# 检查基准目录是否存在且非空 if [ ! -d "$BENCHMARK_DIR" ] || [ -z "$(ls -A "$BENCHMARK_DIR" 2>/dev/null)" ]; then echo "错误: 基准目录 $BENCHMARK_DIR 不存在或为空" echo "请下载Silesia测试集: wget http://mattmahoney.net/dc/silesia.zip && unzip silesia.zip -d /tmp/silesia/" exit 1 fi
DIR_SIZE=$(du -bs "$BENCHMARK_DIR" | cut -f1) if [ "$DIR_SIZE" -eq 0 ]; then echo "错误: 基准目录大小为零" exit 1 fi
OUTPUT_ROW="%-15s %-10s %-10s %20s %25s\n"
# 通用基准测试函数,增加文件系统参数 bench(){ local fs_type=$1 local mount_opts=$2 local label=$3
# 根据文件系统类型选择格式化命令 if [ "$fs_type" = "btrfs" ]; then mkfs_cmd="mkfs.btrfs -O block-group-tree /dev/ram0 -f" elif [ "$fs_type" = "ext4" ]; then mkfs_cmd="mkfs.ext4 -F /dev/ram0" else echo "错误: 不支持的文件系统类型 $fs_type" return 1 fi
# 测量压缩时间 (对于ext4和无压缩Btrfs,这实际是纯复制时间) comp_time=$( hyperfine \ --prepare "umount -q /mnt; $mkfs_cmd; mount -o $mount_opts /dev/ram0 /mnt; sync; echo 3 > /proc/sys/vm/drop_caches" \ --style=none --export-json - --runs 20 \ "cp -r $BENCHMARK_DIR/* /mnt; sync" | jq -r '.results[].mean' )
# 测量解压缩/读取时间 decomp_time=$( hyperfine \ --prepare "umount -q /mnt; mount -o $mount_opts /dev/ram0 /mnt; sync; echo 3 > /proc/sys/vm/drop_caches" \ --style=none --export-json - --runs 20 \ "tar -c /mnt 2>/dev/null| cat > /dev/null" | jq -r '.results[].mean' )
# 检查是否成功获取时间数据 if ! [[ "$comp_time" =~ ^[0-9.]+$ ]] || ! [[ "$decomp_time" =~ ^[0-9.]+$ ]]; then echo "错误: 无法获取有效的时间数据 for $label" return 1 fi
# 获取大小信息 (ext4不支持compsize,使用du替代) if [ "$fs_type" = "btrfs" ]; then usage=$(compsize /mnt | awk '/TOTAL/ {print $2, $3}') else # 对于ext4,Ratio设为1.0,压缩后大小等于原始大小 compressed_size=$(du -bs /mnt | cut -f1) ratio=$(echo "scale=2; $compressed_size / $DIR_SIZE" | bc) usage="$ratio $compressed_size" fi
# 计算速度 (MiB/s) comp_speed=$(echo "scale=6; ($DIR_SIZE / 1024 / 1024) / $comp_time" | bc) decomp_speed=$(echo "scale=6; ($DIR_SIZE / 1024 / 1024) / $decomp_time" | bc)
# 格式化输出 speed=$(printf "%.3f MiB/s | %.3f MiB/s" $comp_speed $decomp_speed) printf $OUTPUT_ROW "$label" ${=usage} "$speed"
umount -q /mnt }
# 输出表头 printf $OUTPUT_ROW "文件系统/算法" "压缩比" "压缩后大小" "压缩速度" "解压缩速度"
# 加载ramdisk模块 (2GB大小以确保能容纳测试数据) modprobe brd rd_size=$((1*1024**2)) max_part=1 rd_nr=1
# 测试ext4 bench ext4 "defaults" "ext4" echo
# 测试无压缩Btrfs bench btrfs "compress=none" "btrfs (无压缩)" echo
# 测试LZO压缩Btrfs bench btrfs "compress=lzo" "btrfs (lzo)" echo
# 测试不同级别zstd压缩Btrfs for i in {1..6}; do bench btrfs "compress=zstd:$i" "btrfs (zstd:$i)" done echo
# 测试不同级别强制zstd压缩Btrfs for i in {1..6}; do bench btrfs "compress-force=zstd:$i" "btrfs (zstd-force:$i)" done echo
# 清理 rmmod brd
echo "测试完成"
|
前决条件
运行此脚本前,你需要安装以下包:
- zsh
- btrfs-progs
- hyperfine
- compsize
- jq
- bc
你还需要下载silesia测试集,并将其解压至/tmp/silesia/下。
silesia数据集是一个专门用来测试压缩算法的数据集,包含了各种格式与编码的内容,可以模拟日常使用中用户可能使用的各类文件,确保压缩算法的鲁棒性、通用性。
你最好不要在/mnt上挂载设备,因为这个脚本即将在/mnt上创建临时文件系统。
为了确保结果准确,测试过程中不要使用电脑。
结果展示

实际上图片中我没有测试完就结束运行了,因为上次测试发现,zstd压缩级别大于5时压缩后大小几乎不下降而读写速率持续下降。
从结果来看,无压缩的btrfs已经在读写速率上存在小幅下降,lzo压缩算法取得了还算不错的压缩率,而读写速率也有所下降。
可是我最看好的是压缩级别为1的zstd压缩。此时压缩率较lzo压缩有较大增长,而读写速率也有小幅下降,大于一的时候,读写速率持续下降,但是压缩率似乎已经达到极限了。
基于此,我最终选择了zstd压缩,压缩级别为1.
持久化设置
我们通过一个使用脚本完成了对压缩算法与级别的选择。可是如果想要应用压缩,需要在每次mount挂载时手动输入参数,否则就不会应用压缩。这对我来说是不可接受的,因为我已经习惯了在dolphin(文件管理器)里点击设备,自动挂载访问了。
那么有没有办法自动化应用挂载参数呢?
有!
答案在于/etc/fstab,linux已经提供了这样一种设置方式。
这个文件记录了某一个特定设备在挂载时应该挂载于什么目录,是什么文件系统,需要使用怎么样的参数。
这个文件非常重要,如果损毁将无法开机。在修改前建议先备份。
你应该会发现该文件每一行格式都如下:
1
| <file system> <dir> <type> <options> <dump> <pass>
|
<file system>可以用/dev/sda1这样的设备名确定,但我更喜欢使用UUID,这样不容易出错。可以用sudo lsblk -f查看:

UUID那一栏的内容,复制下来。
<dir>指定了该文件系统应该挂载于什么目录,它必须存在,否则会挂载失败。需要提前创建好。
<type> 写明文件系统。
<options>是这里的主角。如果你的fstab里已经有条目了,只需要增加compress=zstd:1就好了。
顺便推荐另外几个用于SSD上的btrfs参数:ssd,discard=async,space_cache=v2
<dump> 指定dump工具是否要备份此文件系统,一般我们都不用,置零即可。
<pass>指定需要检查的文件系统的检查顺序。允许的数字是0, 1, 和2。 根目录应当获得最高的优先权 1, 其它所有需要被检查的设备设置为 2. 0 表示设备不会被 fsck 所检查。
btrfs文件系统建议全部设置为0,因为btrfs的fsck是不起作用的。
btrfs支持在线检查,请在启动后使用btrfs scrub检查文件系统。
保存之后,重启生效。
压缩效果
你可以用之前安装的compsize检查压缩效果:

命令为:
-x表示不跨越文件系统,不加上可能会报错:
/boot/EFI/Microsoft/Boot/bg-BG/bootmgfw.efi.mui: Not btrfs (or SEARCH_V2 unsupported).
因为EFI分区使用了fat32文件系统。
从数据可以看出,在我的系统上,btrfs节省了6GB空间,可是要知道目前我整个系统,带上我一些文件一共只使用了19GB左右。(点名Windows)
总体来说,还是很满意的,吞吐量的减少对我的影响也不大。
你学会了吗?快利用酷酷的btrfs特性吧!