查看磁盘类型
1 | $ lsblk -d -o name,rota,type,size,model |
ROTA=1
:这是旋转磁盘。
测试方法
顺序写吞吐测试(逼近最大写入速度)
1 | fio --name=seqwrite --rw=write --bs=1M --size=5G --numjobs=4 --iodepth=32 --direct=1 --runtime=60 --group_reporting |
随机读 IOPS 测试(逼近最大并发处理能力)
1 | fio --name=randread --rw=randread --bs=4k --size=5G --numjobs=4 --iodepth=64 --direct=1 --runtime=60 --group_reporting |
混合读写测试(模拟数据库负载)
1 | fio --name=mixrw --rw=randrw --rwmixread=70 --bs=4k --size=5G --numjobs=4 --iodepth=32 --direct=1 --runtime=60 --group_reporting |
磁盘的测试结果
由于是旋转磁盘,iodepth 总是 1(设成其他值不会生效)
单线程读写文件:
点击展开代码
fio_bs_test.shview raw
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
# 测试参数
DEVICE="./testfile" # 修改为你要测试的文件或设备路径
RUNTIME=30 # 每个测试运行时间(秒)
# BLOCK_SIZES=("4k" "16k" "64k" "256k" "1M" "4M" "16M" "32M" "64M" "128M") # 测试块大小列表
BLOCK_SIZES=("128M" "64M" "32M" "16M" "4M" "1M" "256k" "64k" "16k" "4k") # 测试块大小列表
LOG_FILE="fio_bs_output.log" # 原始输出日志文件
PERFORMANCE_LOG="fio_bs_performance.log" # 性能结果日志文件
# 输出表头
printf "%-8s | %-10s | %-8s | %-10s | %-10s\n" "RW" "BlockSize" "IOPS" "BW(MiB/s)" "AvgLat(ms)" | tee "$PERFORMANCE_LOG"
echo "-------------------------------------------------------------" | tee -a "$PERFORMANCE_LOG"
echo "" > "$LOG_FILE" # 清空日志文件
# 循环测试不同块大小
for RW in read write; do
for BS in "${BLOCK_SIZES[@]}"; do
OUTPUT=$(fio --name=bs_test \
--filename="$DEVICE" \
--rw=$RW \
--bs=$BS \
--size=1G \
--time_based \
--runtime=$RUNTIME \
--numjobs=1 \
--direct=1 \
--ioengine=psync \
--group_reporting)
echo "----------------------------------------------" >> "$LOG_FILE"
echo "$OUTPUT" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
# 提取关键指标
read IOPS BW BWUNIT LAT LAT_UNIT <<< $(echo "$OUTPUT" | awk '
/IOPS=/ {match($0, /IOPS= *([0-9.]+)/, iops)}
/BW=/ {
match($0, /BW= *([0-9.]+)([KMG]iB)\/s/, bwinfo)
bwval=bwinfo[1]; bwunit=bwinfo[2]
}
/clat \(/ {match($0, /avg= *([0-9.]+),/, lat); match($0, /\(([^)]+)\)/, lat_unit)}
END {print iops[1], bwval, bwunit, lat[1], lat_unit[1]}
')
# 延迟单位换算
if [ "$LAT_UNIT" = "usec" ]; then
LAT_MS=$(awk "BEGIN {printf \"%.2f\", $LAT/1000}")
elif [ "$LAT_UNIT" = "msec" ]; then
LAT_MS=$LAT
else
LAT_MS="Unknown"
fi
# 带宽单位换算为 MiB/s
case "$BWUNIT" in
"KiB") BW_MIB=$(awk "BEGIN {printf \"%.2f\", $BW/1024}") ;;
"MiB") BW_MIB=$BW ;;
"GiB") BW_MIB=$(awk "BEGIN {printf \"%.2f\", $BW*1024}") ;;
*) BW_MIB="Unknown" ;;
esac
# 输出结果行
printf "%-8s | %-10s | %-8s | %-10s | %-10s\n" "$RW" "$BS" "$IOPS" "$BW_MIB" "$LAT_MS" | tee -a "$PERFORMANCE_LOG"
done
done
# 删除 fio 创建的测试文件
rm -f "$DEVICE"
echo "测试完成,结果已保存到 $LOG_FILE 和 $PERFORMANCE_LOG"
1 | RW | BlockSize | IOPS | BW(MiB/s) | AvgLat(ms) |
1 | RW | BlockSize | IOPS | BW(MiB/s) | AvgLat(ms) |
当 BlockSize=32M 以后,写入性能基本达到顶峰(110 MiB/s),和旋转磁盘的参数基本一致。
多线程读写同一个文件,BS=64KiB:
点击展开代码
fio_mt_test.shview raw
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
DEVICE="./mt_testfile" # 测试文件路径
RUNTIME=30 # 每个测试运行时间(秒)
THREADS=(1 2 4 8 16 32 64 72 120) # 测试线程数列表
BLOCK_SIZE="64k" # 块大小,可根据需要修改
LOG_FILE="fio_thread_output.log"
PERFORMANCE_LOG="fio_thread_performance.log"
# 输出表头
printf "%-8s | %-8s | %-10s | %-10s | %-10s\n" "RW" "Threads" "IOPS" "BW(MiB/s)" "AvgLat(ms)" | tee "$PERFORMANCE_LOG"
echo "---------------------------------------------------------------" | tee -a "$PERFORMANCE_LOG"
echo "" > "$LOG_FILE" # 清空日志文件
for RW in read write; do
for THREAD in "${THREADS[@]}"; do
OUTPUT=$(fio --name=thread_test \
--filename="$DEVICE" \
--rw=$RW \
--bs=$BLOCK_SIZE \
--size=5G \
--time_based \
--runtime=$RUNTIME \
--numjobs=$THREAD \
--direct=1 \
--ioengine=psync \
--group_reporting)
echo "---------------------------------------------------------------" >> "$LOG_FILE"
echo "$OUTPUT" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
# 提取关键指标
read IOPS BW BWUNIT LAT LAT_UNIT <<< $(echo "$OUTPUT" | awk '
/IOPS=/ {match($0, /IOPS= *([0-9.]+)/, iops)}
/BW=/ {
match($0, /BW= *([0-9.]+)([KMG]iB)\/s/, bwinfo)
bwval=bwinfo[1]; bwunit=bwinfo[2]
}
/clat \(/ {match($0, /avg= *([0-9.]+),/, lat); match($0, /\(([^)]+)\)/, lat_unit)}
END {print iops[1], bwval, bwunit, lat[1], lat_unit[1]}
')
# 延迟单位换算
if [ "$LAT_UNIT" = "usec" ]; then
LAT_MS=$(awk "BEGIN {printf \"%.2f\", $LAT/1000}")
elif [ "$LAT_UNIT" = "msec" ]; then
LAT_MS=$LAT
else
LAT_MS="Unknown"
fi
# 带宽单位换算为 MiB/s
case "$BWUNIT" in
"KiB") BW_MIB=$(awk "BEGIN {printf \"%.2f\", $BW/1024}") ;;
"MiB") BW_MIB=$BW ;;
"GiB") BW_MIB=$(awk "BEGIN {printf \"%.2f\", $BW*1024}") ;;
*) BW_MIB="Unknown" ;;
esac
# 输出结果行
printf "%-8s | %-8s | %-10s | %-10s | %-10s\n" "$RW" "$THREAD" "$IOPS" "$BW_MIB" "$LAT_MS" | tee -a "$PERFORMANCE_LOG"
done
done
rm -f "$DEVICE"
echo "测试完成,结果已保存到 $LOG_FILE 和 $PERFORMANCE_LOG"
1 | RW | Threads | IOPS | BW(MiB/s) | AvgLat(ms) |
1 | RW | Threads | IOPS | BW(MiB/s) | AvgLat(ms) |
当线程数增加,IO 性能随之提高,可能原因是 64KiB 小块数据大量提交到 I/O 队列,操作系统能更好地完成读写路径优化。
但达到8线程的时候,就基本到达性能顶峰了。
注意:fio 多线程写入同一个文件是没有加锁的,如果超过 page cache (一般是 4 KB),那么可能乱序写入。
概念(以 fio 为例)
ioengine
ioengine(I/O 引擎)是 fio 提供以执行读写任务的底层接口。不同的引擎代表不同的 I/O 模型,比如同步、异步、内存映射、零拷贝等。
引擎名称 | 类型 | 特点与用途 |
---|---|---|
sync | 同步 | 默认方式,每次 I/O 都等待完成,适合简单测试 |
psync | 同步 | 使用 pread/pwrite,可指定偏移,略快 |
libaio | 异步 | Linux 异步 I/O,适合高性能 SSD/NVMe |
io_uring | 异步 | 新一代 Linux 异步接口,低延迟、高并发 |
mmap | 内存映射 | 将文件映射到内存,适合大文件顺序访问 |
splice | 零拷贝 | 用于高效数据传输,减少 CPU 和内存开销 |
windowsaio | 异步 | Windows 原生异步 I/O,适合多线程写入 |
net | 网络 | 用于网络 I/O 测试,如 socket 传输 |
sg | SCSI | 用于直接访问 SCSI 设备 |
每种 ioengine 都依赖操作系统提供的底层 I/O 接口。例如:
ioengine 类型 | 操作系统要求 | 是否异步 | 说明 |
---|---|---|---|
sync / psync | 所有系统 | ❌ | 使用标准阻塞 I/O,几乎总是可用 |
libaio | Linux,需安装 libaio 库 | ✅ | 依赖 Linux 的异步 I/O 接口 |
io_uring | Linux ≥ 5.1,推荐 ≥ 5.4 | ✅ | 依赖新内核特性和 liburing 库 |
windowsaio | Windows | ✅ | 使用 Windows 原生异步 I/O |
mmap | 所有主流系统 | ❌ | 使用内存映射,适合顺序读写 |
posixaio | POSIX 兼容系统 | ✅ | 使用 aio_read / aio_write 接口 |
你可以指定任意 ioengine (默认值是 sync / psync),但它是否能运行,必须得到操作系统的支持。这包括内核版本、系统接口、库文件等。如果系统不支持,fio 会报错或自动回退。
查看支持列表:
1 | fio --enghelp |
这会列出当前系统上可用的 ioengine,但注意:列出来 ≠ 能用,还要看运行时是否报错。
实际测试:
1 | fio --name=test --ioengine=io_uring --rw=write --size=1G --bs=1M |
如果不支持,会报错,例如:
1 | fio: pid=132756, err=38/file:engines/io_uring.c:1351, func=io_queue_init, error=Function not implemented |
iodepth
--iodepth
是传递给内核的参数。
如果你不显式设置 --iodepth
,那么 fio 会根据所选的 I/O 引擎(--ioengine
) 来决定默认值
I/O 引擎 | 默认 iodepth |
---|---|
sync / psync / vsync | 1(同步 I/O,只能一个一个处理) |
libaio / io_uring | 1,但可以设置更高以启用异步并发 |
mmap / pread / pwrite | 1 |
windowsaio(Windows) | 1 |
sg(SCSI generic) | 1 |
fio 并不会主动维护队列,队列是内核的特性。
I/O 引擎 | 队列位置 | 是否异步 | 说明 |
---|---|---|---|
psync / sync | 无队列(直接调用) | 否 | 每次写入调用 write(),无排队机制 |
libaio | 内核空间 | ✅ 是 | 使用 Linux AIO,队列在内核中,由 io_submit() 提交 |
io_uring | 用户 + 内核共享 | ✅ 是 | 使用环形缓冲区,用户空间提交,内核空间处理 |
mmap / null | 用户空间 | ❌ 否 | 模拟或跳过实际 I/O,不涉及内核队列 |
- 对于支持异步 I/O 的引擎(如 libaio 或 io_uring),你可以设置更高的 iodepth(如 32、64、128)来模拟高并发负载;
- 对 SSD 或 NVMe 设备,高 iodepth 能显著提升 IOPS 和吞吐量;
- 对机械硬盘,提升有限,但仍可用于测试调度策略和队列行为。
但如果你的 ioengine 是 sync 或 psync,这些是同步阻塞 I/O,根本不支持高并发,所以 iodepth 实际上不会生效。
参数 | 说明 |
---|---|
–name=seqwrite | 定义测试任务的名称为 seqwrite,用于标识输出结果 |
–rw=write | 设置为顺序写入模式(sequential write),数据按顺序写入磁盘 |
–bs=1M | 每次 I/O 操作的块大小为 1MB,适合测试吞吐量 |
–size=5G | 每个线程写入的总数据量为 5GB(不是总共,是每个 job) |
–numjobs=4 | 启动 4 个并发线程(job),模拟多线程写入场景 |
–iodepth=32 | 每个线程的 I/O 队列深度为 32,表示最多可同时挂起 32 个 I/O 请求 本例中每个线程会发起 5G/1M=5120 个 I/O 请求 |
–direct=1 | 绕过系统缓存,直接对磁盘进行读写,更真实地反映设备性能 |
–runtime=60 | 测试持续时间为 60 秒,优先于 –size, 1. 即使数据写完也继续写更多数据直到时间结束 < br>2. 如果没有写完,则时间到就结束 |
–group_reporting | 汇总所有线程的测试结果,输出整体性能指标而不是每个线程单独显示 |
可能的代码实现:
1 |
|
延迟
指标 | 含义 | 描述 |
---|---|---|
slat | Submission Latency | 从 fio 发起 I/O 请求到内核接收该请求的时间。通常很短,单位是微秒(usec)。 |
clat | Completion Latency | 从内核接收请求到 I/O 操作完成的时间。这个是最能反映存储设备性能的部分。 |
lat | Total Latency | 总延迟,即 slat + clat,表示从 fio 发起请求到 I/O 完成的整个过程。 |
结果分析
1 | $ fio --name=seqwrite --rw=write --bs=1M --size=5G --numjobs=2 --direct=1 --runtime=60 --group_reporting |
🧾 测试配置解析
bash
fio –name=seqwrite –rw=write –bs=1M –size=10G –numjobs=1 –direct=1 –runtime=60 –group_reporting
参数 含义
rw=write 顺序写入
bs=1M 每次写入块大小为 1MiB
numjobs=1 单线程写入
direct=1 使用 Direct I/O,绕过页缓存
ioengine=psync 使用同步 I/O(每次 pwrite())
iodepth=1 每次只挂起一个 I/O 请求(同步模式下默认如此)
📊 性能结果概览
指标 数值 说明
IOPS 96 每秒执行 96 次写入操作
带宽 96.2 MiB/s(101 MB/s) 每秒写入约 96 MiB 数据
总写入量 5772 MiB 在 60 秒内完成的写入总量
延迟(avg clat) 10.36 ms 每次写入的平均完成时间
CPU 使用率 usr=0.46%, sys=1.23% CPU 负载极低,瓶颈不在 CPU
⏱ 延迟分布分析
50% 的写入延迟低于 9.9 ms
95% 的写入低于 12.5 ms
99.95% 的写入延迟达到了 22.9 ms
最慢的写入高达 28.2 ms
尾部延迟略高,说明偶尔会有磁盘响应变慢的情况,可能是设备内部缓存刷新或寻址造成。
📈 带宽波动情况
平均带宽:约 96 MiB/s
最小带宽:60 MiB/s
最大带宽:104 MiB/s
标准差:6.2 MiB/s → 表明带宽相对稳定,但仍有轻微波动
🧠 深层解读
✅ 为什么 IOPS ≈ 带宽(MiB/s)?
因为你设置了 bs=1M,每次写入 1MiB 数据,所以:
Code
IOPS × Block Size = Bandwidth
96 IOPS × 1 MiB = 96 MiB/s
✅ 为什么 Direct I/O?
绕过页缓存,测试的是磁盘的真实物理性能,避免被内存加速 “欺骗”。
✅ 为什么使用 psync?
psync 是同步写入,每次调用 pwrite(),适合模拟数据库或日志系统的写入行为。但它无法并发挂起多个请求,限制了吞吐。
📌 性能瓶颈分析
磁盘类型:如果是 HDD,这个结果(96 MiB/s)非常合理;如果是 SSD,则偏低,可能受限于同步 I/O 或单线程。
IO 引擎限制:psync 是阻塞式,无法发挥磁盘的并发能力。
线程数限制:只有一个线程,磁盘可能未被充分利用。