
1. 为什么靠SPDK perf测NVMe能比常规工具快一大截先说我自己的使用场景给一批NVMe SSD做选型测试或者排查某块盘在特定压力下的性能毛刺。传统做法是拿fio跑一轮但fio走的是内核NVMe驱动中间隔了系统调用、中断处理、块设备层调度性能数据很难反映盘本身的真实上限。后来我换成SPDK自带的perf工具也就是spdk_nvme_perf同样一块盘4K随机读IOPS从几十万直接拉到上百万延迟也低了一个数量级。先说清楚SPDK的定位。SPDK全称是Storage Performance Development KitIntel开源的一套存储开发套件核心思路是绕过内核直接在用户态操作NVMe设备。它干的几件关键事情用户态驱动、轮询模式代替中断、无锁数据结构、大页内存管理再加上NVMe本身就是高并发的协议四者叠加后单CPU核就能打出几十万甚至上百万IOPS。perf工具就是这个套件里专门用来测NVMe性能的命令行程序名字叫spdk_nvme_perf在编译完的build/bin目录下。这个工具解决的问题很明确在不用写一行代码的前提下快速压测NVMe SSD的IOPS、带宽、延迟而且能精确控制队列深度、块大小、读写比例、CPU核数等关键变量。适合谁来用一类是存储研发比如你在写用户态驱动或者做NVMe控制器原型验证另一类是运维和测试工程师比如采购SSD前要横向对比不同型号、不同固件版本的性能差异。哪怕你对SPDK完全没接触过只要能编译一个开源项目、能看懂几行命令参数这篇文章里讲的流程你都能直接复现。我下面会把整个流程拆开讲从环境准备、编译、参数含义到实操命令、性能数据解读再到我实际踩过的坑。全部基于我在这类测试中的真实操作不是把README抄一遍。2. 动手前的准备编译环境、依赖项和硬件确认2.1 需要哪些依赖一个命令装齐SPDK的编译依赖比较多但基本都是常规库。以Ubuntu/Debian系为例我在干净系统上执行sudo apt-get install -y gcc g make pkg-config libssl-dev libaio-dev libpciaccess-dev libuuid-dev libjson-c-dev python3几个重点库的作用说一下libpciaccess-dev和libuuid-devSPDK枚举PCIe设备、生成命名空间UUID时用得上缺了会在configure阶段报错。libaio-devSPDK的bdev层有Linux AIO驱动即使你只测NVMe它也会被默认编译进去建议直接装上。libjson-c-dev新版SPDK的JSON-RPC配置依赖它如果你只看perf工具可能用不到但装上免得configure时多一个warning。python3构建脚本和部分测试脚本依赖Python环境。如果是CentOS/RHEL系对应的是yum install -y gcc gcc-c make openssl-devel libaio-devel libpciaccess-devel libuuid-devel json-c-devel python3包名略有不同逻辑一样。2.2 编译SPDK的完整命令SPDK的编译流程是configure加make但有几个选项要留意。我的做法是git clone https://github.com/spdk/spdk.git cd spdk git submodule update --init ./configure --with-vfio-user --without-rdma --disable-tests --disable-examples make -j$(nproc)参数逐一解释--without-rdma如果不需要RDMA网络存储可以关掉RDMA相关依赖省掉libibverbs等库编译更快。--disable-tests --disable-examples只为了用perf工具的话不用编全部测试和示例程序节约时间。git submodule update --initSPDK依赖DPDKsubmodule会拉取对应版本的DPDK源码。这一步别漏否则configure基本必挂。编译完成后验证工具是否正常ls -lh build/bin/spdk_nvme_perf build/bin/spdk_nvme_perf --help如果--help能输出一长串参数说明说明编译成功。有些版本工具名也叫spdk_nvme_perf偶尔也叫perf在build/bin下看实际生成的名字就行。2.3 硬件层面必须提前确认的事SPDK的用户态驱动需要把NVMe设备从内核驱动中解绑再绑定到VFIO或UIO驱动。这要求CPU和主板开启VT-dIntel的IOMMU虚拟化技术否则VFIO用不了。开机进BIOS找到类似Intel Virtualization Technology for Directed I/O的选项确认是Enabled。然后在Linux启动参数里加上intel_iommuon iommupt。改完后重启用dmesg | grep -i iommu能看到IOMMU已开启。还要确认你要测的NVMe盘不是系统盘。因为SPDK perf会在测试前把设备从内核驱动解绑系统盘如果被解绑服务器直接卡死。稳妥的做法是准备一块独立的数据盘系统盘装在另外的盘上。我之前吃过这个亏在测试机上直接跑了perf把系统盘解绑后整台机器失去响应只能硬重启后来专门加了一块测试盘才解决。3. perf工具的核心参数和测试设计思路3.1 参数速查表照着填就行spdk_nvme_perf的参数非常多但实际测试中常用的就那几个。我给每一类测试都整理了一套固定组合用的时候直接改参数值参数含义我的常用取值说明-rPCIe地址列表0000:01:00.0多个设备用逗号分隔-q队列深度32NVMe队列命令数决定并发度-s单次I/O大小40964KB是块设备标准块大小-w负载类型read/write/rw/randread/randwrite/randrw顺序读、顺序写、混合、随即读写-t测试时长秒60压力测试建议60秒以上-c核心掩码0x1用十六进制指定CPU核心-m核心列表-m [0,1,2,3]新版本更推荐用核列表-o输出报告路径perf_report.txt文本格式结果-O清空设备已有数据仅首次测试时加全盘擦除谨慎使用3.2 为什么队列深度、块大小这些参数这么关键NVMe协议本身支持每个队列有最多65535个未完成命令所以队列深度直接影响性能。队列深度太低盘来不及发挥并行处理能力太高了延迟被拉长IOPS反而可能下降。SPDK默认队列深度是128但我在实际测试中发现不同盘的最佳队列深度差异很大有些企业级盘在-q 256甚至-q 512时表现更好消费级盘到了-q 128就基本到顶了。块大小也要结合场景来看4KB块大小是SSD性能测试的事实标准因为对应虚拟化和数据库页的大小。128KB到1MB的块大小适合测顺序带宽能反映大文件写入场景。512B块大小适合测小IO处理能力但在现代NVMe盘上512B的IOPS会被协议本身的命令开销限制一般不用来做对比基准。测试时最好每次只改一个变量比如固定队列深度和块大小只切换读写模式这样数据才有可比性。我见过不少人一上来就放一堆组合最后结果乱成一锅粥根本看不出是哪项参数起的作用。3.3 如何设计一组合理的测试矩阵我的标准做法是跑一套包含6到8个组合的矩阵全部跑完后汇总成对比表4KB随机读队列深度分别取1、8、16、32、1284KB随机写队列深度取32256KB顺序读队列深度取32256KB顺序写队列深度取32混合70%读30%写队列深度取32这套矩阵大概覆盖了日常使用中最典型的场景低队列深度下的延迟特性、高队列深度下的最大吞吐、大数据块的带宽表现、混合读写的调度能力。每组测试60秒整轮跑下来大约10分钟出头效率很高。4. 完整实操从绑定设备到跑出第一份报告4.1 用脚本把NVMe设备从内核解绑SPDK自带的脚本可以帮我们完成设备绑定路径在build/bin同级目录的scripts/setup.sh。运行它会自动把所有NVMe设备从内核驱动解绑然后绑定到VFIO。cd build sudo ../scripts/setup.sh执行完以后可以用sudo ../scripts/setup.sh status查看绑定结果正常应该看到NVMe设备对应的Driver是vfio-pci而不是nvme。我用一条命令确认设备地址lspci -nn | grep -i nvme输出类似01:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd NVMe SSD Controller [144d:a804]前面那个01:00.0就是-r参数要填的PCIe地址完整格式写0000:01:00.0。这里有个细节如果在虚拟化环境里SR-IOV的VF设备会被setup.sh识别成NVMe类型但实际不能跑perf只有PF设备才行。所以测试前先确认你拿到的是物理功能设备。4.2 大页内存的配置SPDK运行需要HugePage没有足够的大页内存会直接回报错。一般NVMe perf测试只需要几百MB配置2GB就非常充裕了。临时生效的方式sudo sh -c echo 1024 /proc/sys/vm/nr_hugepages1024个2MB大页刚好2GB。验证方式grep HugePages_Total /proc/meminfo如果要永久生效在/etc/sysctl.conf里加一行vm.nr_hugepages1024。我建议即使在临时测试机上也直接写进sysctl因为SPDK跑几个小时后内存碎片会让HugePage分配失败重启后又要重设一遍不如一次性配置好。有些新版本SPDK还会检查/dev/hugepages挂载情况没有的话执行mount -t hugetlbfs -o pagesize2M nodev /dev/hugepages。4.3 第一次运行先做清盘再跑随机读绑定完成、大页就绪后就可以跑第一次测试了。我习惯先加-O参数对目标盘做一次全盘清空这能保证后续测试不会受旧数据分布影响。注意-O会把整块盘LBA范围内的已有映射全部废弃属于破坏性操作。sudo build/bin/spdk_nvme_perf -r 0000:01:00.0 -q 32 -s 4096 -w randread -t 60 -c 0x1 -O -o /tmp/nvme_randread_q32.txt跑完以后输出报告内容大概长这样Device Info -------------------------------------------- NVMe SSD at 0000:01:00.0 model: SAMSUNG MZQL21T9HCJR firmware: GDC5602Q Latency(us) ---------------- Average: 189.3125 ... IOPS: 471326.75 Bandwidth(MB/s): 1841.12IOPS就是每秒完成的I/O次数Bandwidth是带宽Latency是平均延迟微秒。如果你的结果里出现几千到几万微秒的平均延迟先检查块大小是否设置过大或者盘本身处于降速状态。4.4 增删队列深度、核数观察性能曲线变化很多人跑完一组就收工了其实perf的核心价值在于对比。我通常会把-q从1逐步调到256每次记录IOPS和延迟画出一个队列深度与性能的曲线。这样能判断盘的饱和度在哪。执行方式循环跑就行比如for q in 1 2 4 8 16 32 64 128 256; do sudo build/bin/spdk_nvme_perf -r 0000:01:00.0 -q $q -s 4096 -w randread -t 30 -c 0x1 -o /tmp/result_q${q}.txt done跑完后把所有result_q*.txt里的IOPS提取出来对比。正常情况下IOPS会随着队列深度上升呈现对数增长然后进入平台期。如果增长曲线上出现明显下降可能是盘内GC垃圾回收策略导致。CPU核数的调整类似-c 0x1是单核-c 0x3是两个核多核时SPDK会将队列分散到不同核上适合验证多队列扩展性。5. 数据解读从perf输出看NVMe盘的“健康状况”5.1 延迟、IOPS、带宽三者如何互相影响这三个指标是同一事件的不同视角。IOPS乘以单次I/O大小就是带宽比如IOPS 47万乘以4KB约等于1.83GB/sIOPS的倒数就是平均完成时间直接反映延迟。需要特别留意的是队列深度越高带宽和IOPS通常越高但延迟也会增加。所以评估一块盘时不要只看IOPS峰值还要看它在低队列深度下的延迟是否够低那才代表日常轻负载的体验。我一般把结果整理成下面这样的速查表负载类型块大小队列深度IOPS平均延迟(us)带宽(MB/s)4KB随机读409618124012.33174KB随机读40963247132667.818414KB随机读4096128521477245.12037128KB顺序读1310723215512206220324KB随机写409632184326173.6720有这张表在手跟供应商谈性能的时候心里就很有底一次测试就能把盘的真实水平看透。5.2 判断性能瓶颈在盘、主板还是PCIe通道一个绕不开的问题测出来的成绩很多时候不是盘的上限而是平台的瓶颈。举个例子现在不少人把NVMe固态插在老主板的PCIe x1插槽上这种情况下4KB随机读的IOPS可能只有十几万远低于盘标称的几十万甚至上百万。原因很简单PCIe x1通道带宽只有约1GB/s以PCIe 3.0为例4KB块大小即使IOPS再高带宽也会被通道卡住。我用perf实测过一块PCIe 4.0盘分别插在x4和x1插槽上的差异插槽通道带宽4K随机读IOPS128K顺序读带宽PCIe 4.0 x4约7.88GB/s约52万约3.9GB/sPCIe 3.0 x1约1GB/s约15万约0.95GB/s所以当你测出来的数据明显低于盘标称规格时先别怀疑盘有问题用lspci -vv -s 01:00.0 | grep LnkSta看看链路状态LnkCap: Port #0, Speed 16GT/s, Width x4 LnkSta: Speed 16GT/s, Width x4LnkSta里的Speed和Width就是当前实际协商到的链路速度和通道数。如果显示Width x1那性能瓶颈基本就锁定了。老平台的BIOS如果不支持NVMe引导但把它当作数据盘用是可以的只是性能会卡在通道宽度上这个物理约束perf再快也绕不过去。5.3 NVMe协议为什么能抗住这么高的并发SPDK能跑出超高IOPS除了用户态驱动NVMe协议本身的设计也很关键。NVMe不再使用传统SCSI的SAS/SATA命令集而是基于PCIe的寄存器级命令提交方式主机侧把命令写入SQSubmission Queue设备处理完把结果写入CQCompletion Queue硬件门铃寄存器负责通知。这避免了每个I/O都触发一次中断再配合SPDK的轮询模式CPU可以用很低的开销支撑极大并发。这也是为什么NVMe协议的队列数量可以做到64K个每个队列深度最大64K命令而SATA的NCQ只能到32深度。perf的核心作用就是把NVMe协议这种并行能力完整暴露出来让你看到盘在协议允许的并发上限下到底能跑多快。6. 疑难杂症排查绑定失败、性能异常和坑位记录6.1 setup.sh脚本绑定失败的常见原因最常见的一种报错是Could not bind to VFIO: No such device or address大概率是IOMMU没开或者BIOS里VT-d没有打开。确认办法是dmesg | grep -e DMAR -e IOMMU如果没有任何输出说明IOMMU内核参数可能没生效重新检查/etc/default/grub中是否有intel_iommuon iommupt然后执行sudo update-grub再重启。另一种情况是盘上还有内核驱动在使用比如nvme模块还在管理设备。这时需要先确保盘没有挂载、没有被RAID卡接管然后再执行setup.sh。如果是带SR-IOV功能的盘VF设备可能已经被VFIO绑定而PF设备绑定失败需要手动指定-g参数只绑定你要测的那个设备。6.2 跑起来后IOPS极低怎么定位先看队列深度和核数。很多新手是单核去跑高队列深度其实遇到-q 128以上时单个CPU核轮询所有队列很容易打满建议直接给两个核用-c 0x3或-m [0,1]。我实测相同的盘从单核切到双核IOPS能提升30%左右。再看大页内存是否够用HugePage不足时perf直接在初始化阶段就退出不太容易误解但如果系统内存紧张SPDK的缓冲池会明显变小间接影响吞吐。建议测试时把不必要的服务停掉用free -g确认内存空间。最后是盘本身状态问题。一块写过大量垃圾数据、且长时间没做Trim的盘跑4K随机写时IOPS会掉得厉害因为每次写都可能触发读取修改写入的GC操作。遇到这种情况下先做一次全盘安全擦除。SPDK自带spdk_nvme_cli工具里面封装了nvme格式化的操作perf自身也提供了-O参数来清LBA数据但不一定会对盘做TRIM。完整的安全擦除更适合用nvme format命令执行。6.3 实测心得老主板和驱动冲突的那些事先说驱动冲突。用setup.sh绑定后想切回原状跑sudo ../scripts/setup.sh reset即可它会把设备重新绑定回nvme驱动。但有些时候reset脚本会失效设备卡在vfio-pci不动这时可以手动解绑再绑定echo 0000:01:00.0 /sys/bus/pci/drivers/vfio-pci/unbind echo 0000:01:00.0 /sys/bus/pci/drivers/nvme/bind另外如果服务器上装了多块NVMe盘而系统盘也是NVMe绑定前一定要看清楚setup.sh会处理哪些设备避免把系统盘一起解绑。我在生产环境踩过一次坑批处理脚本把所有NVMe设备都绑到VFIO系统直接失联后来只能用IPMI重启。老平台方面我遇到过一个特殊案例华硕B85M-V Plus这类老主板刷新魔改BIOS后可以作为启动盘但SPDK绑定后仍会有设备无法枚举的情况。原因是老平台的PCIe枚举方式和ACPI表对PCIe直通的支持不完整需要在内核启动参数上加pcirealloc才能让VFIO正确分配BAR资源。如果解绑后perf找不到设备可以尝试加这个参数。6.4 几个被问得最多的问题汇总Q: perf测出来的数据和厂商标称差很多正常吗 A: 正常。厂商标称值通常是用顶级平台、高队列深度、最优固件配置跑出来的极限值你这边的主板通道、CPU频率、散热、后台进程都会影响结果。perf的价值在于横向对比你在同一环境下测不同盘或不同参数组合数据才有绝对意义。Q: 测试完设备没法挂回文件系统怎么办 A: 先确认设备已从vfio-pci解绑回nvme驱动然后执行partprobe或者重新读取分区表系统一般会重现原有分区。如果之前做了格式化或-O清盘操作分区表已经没了这个步骤救不回来只能重新分区。Q: 同时测多块盘出结果时怎么区分哪块是哪块 A: 报告里每个PCIe地址都有独立一列数据看地址就知道哪块盘的成绩。串在一起-r 0000:01:00.0,0000:02:00.0即可。7. 后续扩展perf之外还能怎么玩这个工具跑通之后还能往几个方向扩展。一是配合spdk_nvme_cli做设备的NVMe协议级操作比如执行固件升级、获取SMART健康信息。固件升级在perf命令里不直接支持但SPDK提供了完整的admin命令封装。二是把输出解析成可视化报告。perf输出是文本我写过一个简单脚本把result_q*.txt解析成CSV然后丢进电子表格工具生成曲线图。这样投给领导或者供应商看比贴一堆文本有说服力得多。三是研究多队列性能用-m指定多个核观察SPDK把队列均匀分布到多核后的线性扩展能力。真正的高并发存储系统通常用多个核心分担不同队列的处理perf可以帮你提前评估盘的队列资源是否够用以及CPU核数对性能的影响曲线。回到老平台插PCIe x1那个问题如果你没有条件换平台又想尽量压榨盘性能可以考虑把测试场景限定在低队列深度、低带宽要求的类型上比如轻量数据库的同步日志写。perf在这种场景下跑出来的结果一样有价值它能告诉你这块盘在受限链路下能提供多稳定的延迟这本身就是选型决策里的一个重要参考。