我正在调查一个问题,其中加密块设备会强加一个巨大的性能损失时写作到它。几个小时的网络阅读和实验并没有给我提供正确的理解,更不用说解决方案了。
简而言之:为什么将 btrfs 放入块设备时可以获得非常快的写入速度 (~170MB/s),而将 dm-crypt/LUKS 放入文件系统和块之间时写入速度却直线下降 (~20MB/s)设备,尽管系统足以维持足够高的加密吞吐量?
设想
/home/schlimmchen/random
是一个 4.0GB 的文件,其中充满了/dev/urandom
早期的数据。
dd if=/dev/urandom of=/home/schlimmchen/Documents/random bs=1M count=4096
阅读速度非常快:
$ dd if=/home/schlimmchen/Documents/random of=/dev/null bs=1M
4265841146 bytes (4.3 GB) copied, 6.58036 s, 648 MB/s
$ dd if=/home/schlimmchen/Documents/random of=/dev/null bs=1M
4265841146 bytes (4.3 GB) copied, 0.786102 s, 5.4 GB/s
(第二次,该文件显然是从缓存中读取的)。
未加密的 btrfs
设备直接用btrfs格式化(块设备上没有分区表)。
$ sudo mkfs.btrfs /dev/sdf
$ sudo mount /dev/sdf /mnt
$ sudo chmod 777 /mnt
写入速度高达~170MB/s:
$ dd if=/home/schlimmchen/Documents/random of=/mnt/dd-test1 bs=1M conv=fsync
4265841146 bytes (4.3 GB) copied, 27.1564 s, 157 MB/s
$ dd if=/home/schlimmchen/Documents/random of=/mnt/dd-test2 bs=1M conv=fsync
4265841146 bytes (4.3 GB) copied, 25.1882 s, 169 MB/s
$ dd if=/home/schlimmchen/Documents/random of=/mnt/dd-test3 bs=1M conv=fsync
4265841146 bytes (4.3 GB) copied, 29.8419 s, 143 MB/s
读取速度远高于200MB/s。
$ dd if=/mnt/dd-test1 of=/dev/null bs=1M
4265841146 bytes (4.3 GB) copied, 19.8265 s, 215 MB/s
$ dd if=/mnt/dd-test2 of=/dev/null bs=1M
4265841146 bytes (4.3 GB) copied, 19.9821 s, 213 MB/s
$ dd if=/mnt/dd-test3 of=/dev/null bs=1M
4265841146 bytes (4.3 GB) copied, 19.8561 s, 215 MB/s
块设备上的加密 btrfs
使用 LUKS 格式化设备,并使用 btrfs 格式化生成的设备:
$ sudo cryptsetup luksFormat /dev/sdf
$ sudo cryptsetup luksOpen /dev/sdf crypt
$ sudo mkfs.btrfs /dev/mapper/crypt
$ sudo mount /dev/mapper/crypt /mnt
$ sudo chmod 777 /mnt
$ dd if=/home/schlimmchen/Documents/random of=/mnt/dd-test1 bs=1M conv=fsync
4265841146 bytes (4.3 GB) copied, 210.42 s, 20.3 MB/s
$ dd if=/home/schlimmchen/Documents/random of=/mnt/dd-test2 bs=1M
4265841146 bytes (4.3 GB) copied, 207.402 s, 20.6 MB/s
读取速度仅受到轻微影响(为什么会这样?):
$ dd if=/mnt/dd-test1 of=/dev/null bs=1M
4265841146 bytes (4.3 GB) copied, 22.2002 s, 192 MB/s
$ dd if=/mnt/dd-test2 of=/dev/null bs=1M
4265841146 bytes (4.3 GB) copied, 22.0794 s, 193 MB/s
luks转储:http://pastebin.com/i9VYRR0p
块设备上 btrfs 上文件中的加密 btrfs
写入加密文件时,写入速度“飙升”至超过 150MB/s。我将一个 btrfs 放到块设备上,分配了一个 16GB 的文件,然后对其lukfsFormat
进行了编辑和挂载。
$ sudo mkfs.btrfs /dev/sdf -f
$ sudo mount /dev/sdf /mnt
$ sudo chmod 777 /mnt
$ dd if=/dev/zero of=/mnt/crypted-file bs=1M count=16384 conv=fsync
17179869184 bytes (17 GB) copied, 100.534 s, 171 MB/s
$ sudo cryptsetup luksFormat /mnt/crypted-file
$ sudo cryptsetup luksOpen /mnt/crypted-file crypt
$ sudo mkfs.btrfs /dev/mapper/crypt
$ sudo mount /dev/mapper/crypt /tmp/nested/
$ dd if=/home/schlimmchen/Documents/random of=/tmp/nested/dd-test1 bs=1M conv=fsync
4265841146 bytes (4.3 GB) copied, 26.4524 s, 161 MB/s
$ dd if=/home/schlimmchen/Documents/random of=/tmp/nested/dd-test2 bs=1M conv=fsync
4265841146 bytes (4.3 GB) copied, 27.5601 s, 155 MB/s
为什么写入性能会这样提升?文件系统和块设备的这种特殊嵌套可以实现什么来帮助提高写入速度?
设置
该问题在运行相同发行版和内核的两个系统上可以重现。然而,我还观察到 System2 上内核 3.19.0 的写入速度较低。
- 设备:SanDisk Extreme 64GB USB3.0 USB 记忆棒
- 系统1:英特尔 NUC 5i5RYH、i5-5250U(Broadwell)、8GB RAM、三星 840 EVO 250GB SSD
- 系统2:联想 T440p、i5-4300M (Haswell)、16GB RAM、三星 850 PRO 256GB SSD
- 发行版/内核:Debian Jessie,3.16.7
- 密码设置:1.6.6
/proc/crypto
对于系统1:http://pastebin.com/QUSGMfiScryptsetup benchmark
对于系统1:http://pastebin.com/4RxzPFeT- btrfs(-tools) 版本为 3.17
lsblk -t /dev/sdf
:http://pastebin.com/nv49tYWc
想法
- 对齐方式是不是据我所知的原因。即使该棒的页面大小为 16KiB,cryptsetup 有效负载起始位置仍会与 2MiB 对齐。
--allow-discards
(对于 cryptsetup 的 luksOpen)没有帮助,正如我所期望的那样。- 虽然对其进行的实验较少,但我观察到通过 USB3.0 适配器连接的外部硬盘驱动器的行为非常相似。
- 在我看来,系统正在写入 64KiB 块。 A系统陷阱脚本我尝试表明至少是这样。
/sys/block/sdf/stat
支持这个假设,因为大量写入被合并。所以我的猜测是,写入太小的块不是原因。 - 将块设备队列调度程序更改为 NOOP 并不成功。
- 将 crypt 放入 LVM 卷中并没有帮助。
答案1
答案(据我现在所知):并发性。
简而言之: 我的顺序写入,无论是使用dd
还是复制文件时(例如......在日常使用中),都会变成伪随机写入(坏)因为四个线程正在并发工作,将并发加密后的加密数据写入块设备(好)。
缓解措施(针对“旧”内核)
可以通过增加 IO 调度程序队列中排队请求的数量来减轻负面影响,如下所示:
echo 4096 | sudo tee /sys/block/sdc/queue/nr_requests
就我而言,这几乎是我的问题中解释的 4GB 随机数据测试吞吐量的三倍(~56MB/s)。当然,与未加密的IO相比,性能仍然低于100MB/s。
调查
多核blktrace
我进一步研究了将 btrfs 放置在 LUKS 加密块设备顶部的问题场景。为了向我展示向实际块设备发出了哪些写入指令,我使用了blktrace
如下方法:
sudo blktrace -a write -d /dev/sdc -o - | blkparse -b 1 -i - | grep -w D
它的作用是(据我能够理解)跟踪 IO 请求,/dev/sdc
其类型为“写”,然后将其解析为人类可读的输出,但进一步将输出限制为操作“D“,这是(根据man blkparse
)”IO 发给驱动程序”。
结果是这样的(参见多核日志输出约5000行):
8,32 0 32732 127.148240056 3 D W 38036976 + 240 [ksoftirqd/0]
8,32 0 32734 127.149958221 3 D W 38038176 + 240 [ksoftirqd/0]
8,32 0 32736 127.160257521 3 D W 38038416 + 240 [ksoftirqd/0]
8,32 1 30264 127.186905632 13 D W 35712032 + 240 [ksoftirqd/1]
8,32 1 30266 127.196561599 13 D W 35712272 + 240 [ksoftirqd/1]
8,32 1 30268 127.209431760 13 D W 35713872 + 240 [ksoftirqd/1]
- 第一列:块设备的major、minor
- 第 2 列:CPU ID
- 第 3 列:序列号
- 第 4 列:时间戳
- 第 5 列:进程 ID
- 第 6 栏:行动
- 第 7 列:RWBS 数据(类型、扇区、长度)
dd
这是将 4GB 随机数据加载到已安装文件系统时生成的输出的片段。显然,至少涉及两个过程。剩余的日志显示所有四个处理器实际上都在处理它。遗憾的是,写入请求不再排序。当 CPU0 正在第 38038416 个扇区附近写入时,随后调度的 CPU1 正在第 35713872 个扇区附近写入。那很糟。
单核blktrace
在禁用多线程并禁用 CPU 的第二个核心后,我做了同样的实验。当然,只有一个处理器参与写入棒。但更重要的是,写入请求是正确顺序的,这就是为什么在其他相同的设置中实现了约 170MB/s 的完整写入性能。
看一下单核日志输出约5000行。
讨论
现在我知道了原因和正确的谷歌搜索术语,有关此问题的信息浮出水面。事实证明,我并不是第一个注意到这一点的人。
- 四年前,一个补丁带来了多线程 dm-crypt到内核。该承诺与我的发现几乎完全吻合。
- 两年前,讨论过补丁提高 dm-crypt 性能,包括写入请求的重新排序。
- 一年前,话题仍在讨论中。
- 最近打了个补丁dm-crypt 排序最终被提交给内核。
- 有一个有趣的电子邮件关于这种现象的性能测试(我没有读太多)。
在当前内核中已修复 (>=4.0.2)
因为我(后来)发现内核提交显然针对这个问题,我想尝试更新的内核。 【自己编译之后发现已经在了debian/sid
】事实证明问题确实解决了。我不知道该修复出现在哪个确切的内核版本中,但是原始提交将为任何感兴趣的人提供线索。
作为记录:
$ uname -a
Linux t440p 4.0.0-1-amd64 #1 SMP Debian 4.0.2-1 (2015-05-11) x86_64 GNU/Linux
$ dd if=/home/schlimmchen/Documents/random of=/mnt/dd-test bs=1M conv=fsync
4294967296 bytes (4.3 GB) copied, 29.7559 s, 144 MB/s
向本次提交的作者 Mikulas Patocka 致敬。