尧图精选

SPDK perf实战指南:从环境准备到数据解读,测透NVMe极限性能

🕒 发布时间:2026/9/12 7:55:18 📁 来源:尧图网络
第一次在项目里用到SPDK perf是帮客户验证一批NVMe SSD的极限性能。当时客户拿着fio跑出来的数字问我“这盘标称500K IOPS为什么我只能跑到300K”这个问题很有代表性。fio跑的是内核存储栈下的性能而盘本身的硬件极限往往要绕过内核才看得到。SPDK perf就是用来做这件事的。如果你也遇到过“标称性能永远跑不出来”的疑惑或者正在做SSD选型、固件验证、NVMe-over-Fabric方案压测这篇文章应该能帮上忙。我会从原理、环境准备、perf参数、数据解读到硬件平台上的各种坑把SPDK NVMe测试工具perf完整拆一遍。文章偏实战命令都是我自己用过的照着操作基本能复现。1. 为什么测NVMe极限性能大家非要用SPDK perf1.1 用户态驱动到底改变了什么先聊一个很多人忽略的前提SPDKStorage Performance Development Kit并不是一个“测试工具”而是一套存储开发套件。它最核心的设计是把传统内核态的存储驱动搬到用户态来做。perf只是SPDK官方提供的一个示例应用但因为太好用慢慢成了大家默认的基准测试工具。传统内核NVMe驱动的IO路径大概是这样的应用发IO请求经过VFS、文件系统、块层、内核NVMe驱动驱动把请求写到设备的提交队列然后靠中断通知CPU处理完成事件。每一次IO都有系统调用、上下文切换、锁竞争、中断处理的开销。单看每一步都还好但高并发下这些开销会被无限放大。SPDK的做法完全不同应用直接通过用户态驱动操作PCIe设备的MMIO寄存器把请求提交到NVMe提交队列后CPU用轮询polling的方式不停检查完成队列。没有中断没有系统调用没有锁竞争。IO路径上几乎只有“提交请求—轮询完成”这一件事。用生活化的类比来说内核驱动模式像去银行大厅排队办完业务还要等叫号SPDK模式像是你塞给业务员一张纸条然后一直站在窗口盯着他他办好你立刻拿走。省掉了叫号系统的等待时间和被别的业务插队的概率每笔IO都少了一大截固定开销。1.2 perf和fio测的不是一回事很多人第一次接触SPDK perf时会把它和fio做对比然后纠结“到底该用哪个”。这是理解偏差。fio是通用的IO负载生成器它走的是Linux内核的IO栈测的是“操作系统存储栈硬件”的整体性能。SPDK perf走的用户态驱动测的是“NVMe设备用户态驱动”这条最短路径的性能上限。这两者不是替代关系而是不同层级的测量工具。我做测试时通常两个都跑fio的结果用来评估“这盘在这台服务器上实际能用出多少”SPDK perf的结果用来评估“这块盘本身到底能跑多快”。两者的差值恰好就是内核IO栈的额外开销。这个数据对存储方案设计很有参考价值后面第4章我会专门讲怎么用perf和fio做对比实验。1.3 这个工具适合谁不适合谁SPDK perf的适用场景非常明确主要是这几类SSD固件开发或验证需要量化固件在不同队列深度、IO大小下的表现服务器存储选型想确定某块盘在“最小软件开销”下能提供多少IOPS和带宽NVMe-oF方案压测perf支持RDMA、TCP等传输层可以直接测NVMe over Fabrics这条链路想深入理解NVMe协议或者做内核存储栈优化的人反过来如果你需要验证文件系统行为、应用层真实延迟或者跑的是数据库这类复杂负载SPDK perf就不合适了。它测的是块设备裸性能不经过文件系统这和你的生产环境差距很大。工具选型的关键在于“明确知道自己想测哪一层”。2. 诚实的性能测试从环境准备开始HugePage、设备接管与CPU隔离SPDK perf的环境准备比fio麻烦不少但每一步都有它的道理。我见过太多人在这一步翻车命令还没跑就报各类错误。其实把环境理顺了perf本身非常简单。2.1 HugePage先给DPDK备好内存池SPDK从DPDK那继承了一套内存管理机制IO数据缓冲和DMA映射都从预分配的大页内存池里取。为什么一定要用大页因为普通4KB页在做DMA映射和IO提交时TLB页表缓存容易被打爆频繁查页表会把轮询模式的低延迟优势抵消掉。用2MB甚至1GB大页地址转换开销几乎可以忽略。配置2MB大页的标准操作如下# 预留4096个2MB大页一共8GB echo 4096 /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages # 挂载hugetlbfs mkdir -p /mnt/huge mount -t hugetlbfs -o pagesize2M hugetlbfs /mnt/huge如果你想要更极致的大页可以在内核启动参数里加hugepagesz1G hugepages4预留4个1GB大页效果比2MB更好。测试机内存不紧张的话建议直接上1GB大页。这里有个实操细节DPDK跑起来时会检查大页目录的访问权限。如果SPDK是用root跑的一般没问题但如果你用普通用户跑记得把/mnt/huge的权限放开或者把用户加入相应组否则会看到“cannot open hugepage”之类的错误。预留大页失败最常见的原因是内存碎片化物理内存不够连续的大块区域。释放一些不必要的进程再试一般能解决。另外不要指望swap来兜底大页内存是不能被换出的。2.2 把NVMe设备从内核驱动“借”过来默认情况下Linux内核的nvme驱动会占用NVMe设备系统盘和普通数据盘都是。SPDK要直接访问PCIe设备的BAR空间必须先把设备从内核驱动解绑改绑到vfio-pci或uio驱动上。SPDK官方提供了脚本一条命令搞定大部分工作cd spdk sudo scripts/setup.sh这个脚本会自动加载vfio-pci驱动把所有非系统盘的内核驱动设备解绑重新绑定到vfio-pci。执行完可以用下面的命令确认状态sudo scripts/setup.sh status lspci -k | grep -A 2 -i nvme正常情况下你会看到类似“Kernel driver in use: vfio-pci”的输出。此时原来/dev/nvme0n1这样的设备节点会消失这是正常的设备已经被“借”走了。这里有两件事必须提醒系统盘绝对不能这么操作。如果整台测试机只有一块NVMe系统盘你需要再加一块独立的测试盘或者换一台系统装在SATA盘上的机器。用vfio-pci需要有IOMMU支持。BIOS里开启VT-dIntel平台或IOMMUAMD平台内核启动参数加上intel_iommuon iommupt。如果你的老平台BIOS里没有VT-d选项或者你想绕过IOMMU可以改用uio_pci_generic驱动。手动绑定方式如下# 先确认uio_pci_generic模块已加载 modprobe uio_pci_generic # 用DPDK的绑定脚本手动指定驱动 dpdk-devbind.py -b uio_pci_generic 0000:01:00.0不过uio_pci_generic不支持某些特性稳定性也不如vfio-pci能用IOMMU还是优先用IOMMU。这个取舍在后面第5章讲老平台时会再提到。2.3 用isolcpus给perf留出专用核SPDK的IO路径是轮询模式负责轮询的线程必须独占CPU核。如果这个核还被系统调度器拿去做别的事情线程一被抢占延迟毛刺就会非常难看测出来的尾延迟数据完全不可信。所以我在测试机上都会做CPU隔离在内核启动参数里加上isolcpus2,3 nohz_full2,3 rcu_nohz_full2,3意思是从Linux调度器中隔离出核2和核3专门留给perf跑。同时加上前面说的intel_iommuon iommupt完整的内核cmdline类似这样GRUB_CMDLINE_LINUX... intel_iommuon iommupt isolcpus2,3 nohz_full2,3 rcu_nohz_full2,3改完记得更新grub并重启。除了核隔离还有两个影响巨大的设置CPU频率和PCIe电源管理。轮询线程如果跑在一个忽高忽低的频率上性能数据会大幅抖动。建议强制性能模式cpupower frequency-set -g performancePCIe的ASPM主动电源管理能关就关最简单的方式是内核参数加pcie_aspmoff。这个我在第5章会结合实际的controller reset问题详细讲它不只是省电那么简单还会直接影响NVMe链路的稳定性。还要多说一句NUMA。不同核访问同一个PCIe设备的距离是不一样的跑perf时最好选择NVMe设备所在NUMA节点上的核。用numactl -H查看节点拓扑用lspci -vvv确认设备挂在哪个numa node上一般会显示NUMA node: 0或类似信息。跨NUMA测试不是不能跑但性能会打折扣而且数据不具备和同NUMA测试直接对比的资格。2.4 跑通最小验证用例环境准备好后先跑一个最简命令验证整条链路是否通畅sudo app/spdk_perf/spdk_perf -q 16 -s 4096 -w randread -t 10 -c 0x1 -r trtype:PCIe解释一下各参数-q 16表示队列深度16-s 4096表示4KB IO大小-w randread表示4K随机读-t 10表示跑10秒-c 0x1表示用核0-r trtype:PCIe表示测本地PCIe NVMe设备。跑通后输出里会看到IOPS、带宽和延迟信息。如果这里报错优先排查这几个问题报错现象常见原因处理方式No available hugepage大页没预留或权限不足检查nr_hugepages和/mnt/huge挂载EAL: Error - exiting with code 1DPDK大页初始化失败确认大页预留成功用root运行Could not access device设备没绑定vfio-pci重新执行setup.shlspci -k检查No such deviceTransport ID写错核对trid格式和设备BDF号第一次跑通之前不要急着调参先把环境和设备链路确认好。perf这工具本身不复杂复杂的是让它在正确环境下运行。3. perf命令逐项拆解从IOPS曲线到延迟分布构建测试矩阵3.1 核心参数吃透先别急着手抖SPDK perf的常用参数其实不多但每个都很关键。不同版本参数略有差异以你这台机器上spdk_perf -h的输出为准下面的表是核心参数的通用语义参数含义示例-q / --io-depth每个核的队列深度-q 32-s / --io-sizeIO大小单位字节-s 4096-w / --rwIO模式read/write/randread/randwrite/rw/randrw-w randread-M / --rwmixread混合模式中读的百分比-M 70-t / --time测试时长单位秒-t 60-c / --core-maskCPU核掩码-c 0x3-r / --tridTransport ID指定设备-r trtype:PCIe这里最容易被误解的是-q。SPDK perf里的队列深度是“每个核的队列深度”。如果你用-c 0x3跑两个核每个核-q 32那么实际打到设备上的总队列深度是64而不是32。很多人在设计测试矩阵时没算清这笔账导致测出来的数据和预期对不上。-r参数在不同场景下区别很大。测本地NVMe时用-r trtype:PCIe就行。测NVMe-oF时要指定远端的传输类型和地址比如RDMA的写法类似-r trtype:RDMA adrfam:IPv4 traddr:192.168.1.10 trsvcid:4420如果你的SPDK编译时开了TCP传输也可以用trtype:TCP跑NVMe/TCP。perf之所以在NVMe-oF测试圈里这么流行就是因为它这套trid机制对本地和远端设备一视同仁命令风格完全一致。3.2 以“队列深度扫描”为核心设计测试矩阵NVMe协议本身就是一个高并发协议它允许每个队列有很深的队列深度。现代SSD内部普遍有多通道、多Die并行结构只有把队列深度拉高设备才有机会把内部并行度调度起来。这也是为什么perf测试一定要做“队列深度扫描”而不是只测一个QD下的数字。我常用的测试矩阵长这样测试目的固定参数变化参数4K随机读性能曲线-s 4096 -w randread-q 从1扫到1284K随机写性能曲线-s 4096 -w randwrite-q 从1扫到128大块顺序读带宽上限-q 32 -w read-s 从64K到1M大块顺序写带宽上限-q 32 -w write-s 从64K到1M混合读写比例影响-s 4096 -q 32 -w randrw-M 从70扫到30每个组合的测试时长我建议至少30秒稳定场景用60秒。太短的数据抖动很大尤其是写操作盘内垃圾回收GC的影响会让10秒内的采样数据完全不可复现。批量跑的时候写个简单的bash循环就行for qd in 1 2 4 8 16 32 64 128; do sudo app/spdk_perf/spdk_perf -q $qd -s 4096 -w randread \ -t 60 -c 0x1 -r trtype:PCIe result_randread_qd${qd}.txt sleep 5 done轮与轮之间隔几秒让上一轮的IO彻底排空。如果你连续跑高强度写入盘温会上升NAND温度过高时固件会主动限速数据就会飘。让盘凉一凉再跑下一组数值会稳定很多。3.3 混合读写与多核场景怎么测混合读写是模拟真实业务负载最有用的模式。命令上很简单比如70%读、30%写sudo app/spdk_perf/spdk_perf -q 32 -s 4096 -w randrw -M 70 -t 60 -c 0x1 -r trtype:PCIe但混合读写的测试结果波动通常比纯读纯写大得多。原因在于写路径会触发垃圾回收而垃圾回收的时机不是均匀分布的。一块刚做完Secure Erase的盘和一块写满数据的脏盘混合读写性能可能差30%以上。所以混写测试我建议每个用例至少跑3次取中位数。如果只看一次的结果你很有可能被一个忽高忽低的数字误导。多核场景的测试逻辑和单核不同。你要么固定每核队列深度用更多核来冲刺极限IOPS要么固定总队列深度把线程数从1加到8观察延迟和IOPS的变化。前者适合看峰值后者适合看扩展性。比如想用8个核、每核32队列深度总共256队列深度去冲随机读极限sudo app/spdk_perf/spdk_perf -q 32 -s 4096 -w randread -t 60 -c 0xff -r trtype:PCIe如果你发现从4核加到8核IOPS几乎没涨这时候别急着怪SPDK先检查设备的固件有没有多队列处理瓶颈再检查是不是跨了NUMA节点访问。4. 不要只盯着IOPS数据怎么解读测试结果怎么才算可信4.1 延迟-队列深度曲线才是设备品质的照妖镜刚到手的perf结果很多人第一眼只看最大IOPS。这个习惯得改。IOPS这个数字太容易被“制造”出来了——调高队列深度IOPS自然涨但代价是延迟也在涨。存储性能分析有一个基本公式IOPS 队列深度 / 平均延迟。举个例子假设平均延迟是200微秒0.0002秒队列深度32那么理论IOPS就是32 / 0.0002 160K。当设备内部没有瓶颈时IOPS会随队列深度线性增长一旦设备内部资源饱和延迟会加速上升IOPS增速放缓甚至停滞。这个“拐点”就是设备真实能力的边界。所以做队列深度扫描时不仅要记录每个QD下的IOPS还要同时记录平均延迟和尾延迟。一块好的企业级SSD在饱和点附近延迟依然稳定一块消费级SSD可能在队列深度16时平均延迟还行到32时P99延迟突然暴涨。SPDK perf不同版本输出的延迟字段不太一样。老版本只给平均延迟新版本会带P99甚至更细的百分位。如果只有平均延迟也不要紧你只要对比每个QD下的平均延迟变化趋势同样能判断出设备的饱和点。测尾延迟更精确的办法还是用fio的clat_percentiles这个是另一套玩法了。4.2 多核扩展性与核数-队列深度的配合多核测试能回答一个关键问题这块盘的性能上限是设备固件决定的还是软件栈决定的SPDK perf的价值恰恰在于它的软件栈开销极小如果你用perf测多核扩展性都不好那问题基本出在设备侧。操作方式很简单固定每核队列深度比如-q 32把核掩码从0x11核、0x32核、0xf4核、0xff8核依次变化记录每个配置下的总IOPS。不同设备的表现差异很大。有的企业级NVMe SSD在1核时就能跑到700K IOPS4核时冲到2M以上扩展性接近线性有的消费级盘1核跑到300K就到顶了加核数几乎不涨。这种差异反映的是设备控制器内部多个队列并行处理能力的差距。还有一个容易踩的坑如果你在做多核测试时没有固定总队列深度而是一味加核总队列深度也在成倍增加那你实际测的是“队列深度上升”和“核数上升”的混合效应数据解释起来会很棘手。所以我一般会把两种场景分开测一种是“总QD固定通过增加核数分散负担”另一种是“每核QD固定通过增加核数提升总QD”然后分别分析。4.3 和fio做对照量化软件栈损耗用SPDK perf测出设备极限后下一步我会在同一台机器上跑一遍fio两组数字一对比就能算出内核IO栈到底“吃掉”了多少性能。这个数据对做存储方案设计非常有用。fio的对照实验要注意对齐条件否则对比没有意义。核心参数要尽量与perf保持一致fio --namerandread \ --ioenginelibaio \ --iodepth32 \ --numjobs1 \ --rwrandread \ --bs4k \ --size20G \ --direct1 \ --runtime60 \ --time_based注意几点--iodepth对应perf里单个核的-q--numjobs对应perf里的核数--direct1绕过page cache--size要大于设备DRAM缓存否则读命中了盘内缓存数据虚高。举个例子我测过一块支持1.6M随机读IOPS的企业级盘SPDK perf单核跑出约380K IOPSfio用同样的单核配置只能跑到310K左右差幅接近20%。这20%就是中断处理、内核驱动、块层调度的额外开销。换个配置差幅可能更大内核版本、IO调度器、CPU中断绑核都会影响这个差值。做这个对比实验的意义在于当你向业务方承诺性能指标时不能只报SPDK perf的数字而要报fio的数字因为生产环境跑的是真实IO栈。5. 硬件平台和热词里那些坑PCIe插槽带宽、老主板BIOS与stornvme复位5.1 NVMe盘插到x1槽上的问题最近老看到有人问“NVMe固态插PCIe x1”能用吗能不能跑满速。答案很直接能识别但性能会被严重限制。问题出在PCIe通道数量上。一条PCIe 3.0 x1链路理论单向带宽约985MB/s而主流NVMe SSD走PCIe 3.0 x4可以到3.5GB/s以上。如果你把盘插在x1槽上顺序读就被卡死在1GB/s左右连好一点SATA SSD的优势都体现不出来。不同PCIe版本的x1和x4带宽大致如下PCIe版本x1 单向带宽x4 单向带宽PCIe 2.0约500MB/s约2GB/sPCIe 3.0约985MB/s约3.94GB/sPCIe 4.0约1.97GB/s约7.88GB/s要判断盘实际跑在什么链路上不要只看插槽形状用命令看最准lspci -vvv -s 01:00.0 | grep -E LnkCap|LnkSta如果输出显示LnkSta: Speed 8GT/s, Width x1说明链路协商在x1上。换一根x4转接卡或者换到主板上走CPU直连的x16插槽问题就解决了。顺带提醒一句主板上的PCIe插槽不是所有都直连CPU。消费级主板通常只有前两根x16形状的插槽走CPU直连其余走芯片组PCH。PCH本身通过DMI总线连CPU带宽有限所有走PCH的设备共享这路带宽。测NVMe极限性能时尽量把盘插在直连CPU的插槽上否则数据会受其他设备干扰。5.2 老主板的BIOS与SPDK测试之间的关系“华硕B85M-V Plus NVMe BIOS”这个搜索词我太熟悉了。老主板没有NVMe引导模块想让NVMe盘做系统盘启动得刷修改版BIOS或借助第三方引导。但这只是“从NVMe启动系统”的需求和SPDK测试完全是两码事。很多玩老平台的人被我这句话解救过SPDK测试不依赖BIOS认识NVMe协议。SPDK是在操作系统运行起来之后通过PCIe配置空间和BAR映射直接操作设备的。只要BIOS能初始化PCIe总线能从其他设备比如SATA盘启动LinuxNVMe盘插在PCIe插槽上能被lspci认出来就能用SPDK perf测试。所以如果你手头正好有一台B85老机器完全可以用它当NVMe测试机省去刷BIOS的风险和折腾。真正需要关注的只有一个点BIOS里有没有VT-d/IOMMU选项。没有的话就用前面提到的uio_pci_generic绑定方式绕开对IOMMU的依赖。我实际在Haswell平台上跑过SPDK性能数据和新平台并没有本质差异。老平台的PCIe 3.0链路对NVMe测试来说够用了只要转接卡别太劣质数据依然可信。这套玩法成本很低对预算有限又想入门SPDK的人来说是个不错的路径。5.3 stornvme.sys controller reset对测试平台的启示“stornvme.sys controller reset”是Windows下很典型的一类NVMe问题。stornvme.sys是微软自带的NVMe驱动当它检测到控制器长时间无响应或IO超时就会触发控制器复位Controller Reset。表现为系统卡顿、事件查看器里出现警告严重的甚至蓝屏。从硬件测试的角度看这个现象背后往往不是“驱动不行”而是平台链路稳定性出了问题。常见诱因有三个ASPM电源管理导致链路进入低功耗状态后唤醒失败固件在高负载或特定IO模式下出现异常超时触发复位转接卡接触不良、PCIe插槽信号质量差、供电不足这对我们做SPDK测试有什么启示太多了。我自己的习惯是任何一台测试机跑SPDK perf之前先做一轮平台加固BIOS里关闭ASPM、C-State、CPU节能EIST内核参数加pcie_aspmoff禁用NVMe的APST自主电源状态转换内核参数加nvme_core.default_ps_max_latency_us0APST是NVMe协议里的低功耗特性盘在空闲时会自动进入省电状态但从省电状态恢复的延迟不稳定会造成延迟毛刺甚至IO超时。做性能测试时必须禁掉它。用nvme-cli也可以临时关闭nvme set-feature /dev/nvme0 -f 0x0c -v 0这些设置在长时间压测中尤其重要。跑一个48小时的稳定性测试如果中途因为链路唤醒失败掉一次盘整轮数据就废了。我在实际项目中遇到过一次类似问题排查到最后就是一条PCIe延长线信号质量差导致的偶发性链路错误换线后一切正常。stornvme.sys controller reset这个现象也提醒我们NVMe设备对平台电气特性的敏感度远超普通SATA设备。做评测时如果系统里同时有Windows和Linux双系统我建议两边都检查一下ASPM和电源管理设置避免“Windows下正常、Linux下性能异常”这种让人抓狂的对比问题。最后再分享一个长期测试的好习惯perf本身不复杂复杂的是让每次测试的数据可以被解释、被复现。我现在的习惯是每个结果文件的命名里都带上完整的环境标签盘型号、固件版本、PCIe链路宽度和速率、CPU核掩码、队列深度、IO大小、读写模式、测试时长。比如SN630-3B2QGXQ7_pcie3_x4_qd32_bs4k_randread_60s.txt这样的命名虽然啰嗦但三个月后翻出来你依然能一眼看出这个数字的测试条件。做SSD评测和存储调优的人都明白一个没有环境信息的性能数字几乎等于没有测。希望这篇文章能帮你少走一些弯路把SPDK perf用得更顺手。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →