聊聊微软最近更新的 NVMe 原生驱动

1. 引言:向 SCSI 翻译层,开战!

NVMe 1.0 在 2011 年发布之后,Windows 存储栈很长一段时间走的是一条务实但不够纯粹的路线,具体做法就是把原生 NVMe Command Set 先塞进 NVMe-to-SCSI translation layer,再通过 Storport Miniport 驱动把命令翻译成 SCSI CDB,最后借助 Class Driver 把请求落到设备侧

这种路线的优势是兼容性,代价是不可忽视的协议开销,单个 I/O 往往要跨越好几层队列才能真正走向设备,包括 Command Translation、PRP 到 SGL 的地址映射转换,还有额外的上下文切换

到 2025 年末,Microsoft 在 Windows Server 2025 与 Windows 11 25H2 推出 Native NVMe Support,把 I/O 路径从根上重构,Windows 终于开始按 NVMe-native 的方式使用 NVMe 了,感动

2. 啰嗦的 stornvme.sys

2.1 背景知识:SCSI LUN 与 NVMe Namespace

LUN,Logical Unit Number 本质上是 SCSI 协议栈里的一段寻址编码,用来在同一个物理 Target 之下区分多个 Logical Unit,也就是把一台物理设备或一套阵列主控暴露成多个逻辑资源,这套设计可以追溯到 1980 年代的 SCSI-1 规范,显然当时的核心诉求并不是极致并发,而是让 Initiator 能在一条总线模型里稳定识别设备与其内部的逻辑单元

SCSI 的寻址层级大体长这样(感谢 GPT 帮我画图),重点是 Target ID 与 LUN 的两级组合

1
2
3
4
5
6
Initiator (Host)
└─ SCSI Bus (ID 0-70-15) ← Target ID 物理设备标识
└─ Target 例如磁盘阵列主控
├─ LUN 0 ← 逻辑单元 0 必须存在,以响应 INQUIRY 等管理命令
├─ LUN 1
└─ ... LUN N

约束来自 LUN 字段的编码方式,在 SAM-3 与 SPC-4 这类标准里,经典的 8 位 LUN 编码把 bits 0-2 用作 Address Method,把 bits 3-7 用作 LUN 编号,编号空间落在 0 到 255,其中 LUN 0 被协议语义强制要求存在,常被视为 Target 自身的管理接口,用来可靠响应 INQUIRY 这类管理命令;与计网类似,255 还经常被当作广播或保留值处理,最终真正可用的范围落在 1 到 254

这在传统 SAN Storage Area Network 环境里会直接变成硬上限,高端企业阵列在单个 Target 下最多暴露 254 个 LUN 并不罕见,例如 Dell SC Series 这类产品就会踩到这条线,实际运维里常见的绕法是多建几个 Target,或者在更上层做 LUN 聚合与虚拟化,但这些方案本质都是在绕过寻址空间不足这个老问题

NVMe 没有沿用 Target 与 LUN 这套层级,而是把寻址简化成 Controller 之下的 Namespace,整体结构是扁平化的 Namespace 寻址模型(感谢 GPT 帮我画图):

1
2
3
4
5
Host
└─ NVMe Controller (PCIe Function)
├─ Namespace 1 NSID=1
├─ Namespace 2 NSID=2
└─ ... Namespace N

Namespace ID NSID 是 32 位无符号整数,范围从 1 到 0xFFFFFFFF,理论上能提供 42 亿以上的命名空间

Moreover, 更关键的是语义。NVMe 的 Namespace 不只是一个编号,其往往对应独立的数据容器与更清晰的隔离边界,每个 Namespace 可以拥有独立的 I/O 上下文,也包括独立的 SQ/CQ 组织方式与端到端数据保护语义

2.2 stornvme.sys 实现现状

传统 Windows NVMe 实现的核心是 stornvme.sys 这个 Storport Miniport Driver,典型路径可以用下面这条线概括:

1
2
3
ApplicationWin32 APIKernel I/O ManagerStorport.sysstornvme.sysNVMe Controller

SCSI Pass-Through Layer

问题并不在于实现是否成熟,而在于抽象层不匹配导致的各种乱七八糟的结构性损耗:

  1. NVMe 的 Submission Queue 与 Completion Queue 本来就是为了多队列并行和低延迟而设计,但翻译到 SCSI 的单一命令队列模型后,队列语义会被拍扁,NVMe 的 64K Queue Depth 能力很难完整发挥,SCSI 侧还存在 254 LUN 的历史约束。正如我在文章前面说的,254 LUN 限制属于寻址空间约束,64K Queue Depth 属于并发能力指标,两者原本是不同维度的问题,但在 Windows 的 SCSI 翻译路径里,NVMe Namespace 被迫映射成 SCSI LUN,多队列语义又被压缩成接近单队列的模型,于是寻址与并发两个维度在实现上发生耦合,最终变成一对苦命鸳鸯

  2. 再往下看数据描述符的开销,NVMe 侧常用 PRP,Physical Region Page 链描述分散 I/O,SCSI 侧常用 SGL,Scatter-Gather List。翻译层需要在 PRP 与 SGL 之间翻译,大 I/O 场景下显然会有较大 CPU 开销

  3. 原子性也会被稀释,像 NVMe Compare-and-Write 这种需要 e2e 保证的原子操作,落到 SCSI 语义里往往只能拆成多步命令序列

3. nvmedisk.sys 上位

Native NVMe 最重要的变化就是绕开 Storport 层,让 I/O Manager 直通 NVMe 驱动,核心驱动变成 nvmedisk.sys,并把 SQ/CQ 的管理收回到 NVMe-native 的语义里(这点很重要),围绕 NVMe 的多队列模型组织并发与亲和性

1
2
3
ApplicationI/O Managernvmedisk.sysNVMe Controller

Native SQ/CQ Management

在 CPU 亲和性上的路径更偏向 Per-CPU Queue Affinity,同时把 I/O 请求按 NUMA 节点贴近本地 CPU 核心的 SQ,跨 NUMA 访问带来的额外延迟会明显减少

在 Doorbell 更新上,采用 Write-Combining 的内存映射来批量更新 SQ Tail Doorbell,从 PCIe 视角看就是减少不必要的 TLP 往返,让提交路径变清晰

在中断侧,围绕 CQ Head Doorbell 做智能中断聚合与延迟控制,在延迟与吞吐之间做更细颗粒度的权衡

4. 为什么宣传提升与实际体验脱节?

4.1 微软的宣传

Microsoft 的内部基准测试给了一个很直观的信号,在 PCIe 5.0 x4 的 NVMe SSD 上,例如 Solidigm P5336,启用 Native NVMe 后 4K 随机读 IOPS 从 1.8M 拉到 3.3M 以上,增幅约 83%

官方给的图表尾延迟看起来更夸张,99.99% Latency 从 120μs 下探到 45μs 级别,利好高频交易、实时 AI 推理这类抖动敏感场景

队列深度的饱和点也明显后移,传统架构在 QD=128 左右就容易出现平台期,Native NVMe 在 QD=1024 仍能保持更线性的扩展趋势

4.2 用户实测

从全球技术社区的密集反馈来看,现实体验与宣传口径存在明显落差,很多案例反映性能提升不明显、部分关键场景出现性能倒退、兼容性不好等等;宣传语境里常见的 80% IOPS 提升,多数来自 DiskSpd.exe 在高队列深度与高线程数条件下的极端压测,而大量日常与消费级负载处于低队列深度与低并发,收益很难兑现

HOMOLAB 曾经号召群友进行测试,挂一张他们的测试结果:

《性能倒退30%:Windows 原生NVME驱动性能实测》:

从平均结果来看,在绝大多数情况下,新NVME驱动没有带来任何的性能正面提升,尤其是对消费级产品而言.

只有在你使用PCIe5.0企业级硬盘,且转接卡与主板能稳定运行在5.0的前提下,使用IOMeter进行等效OIO256以上的负载,才能获得超过10%的性能提升.

除开这一苛刻场景之外的其他情况,数据均呈现出清一色的性能负提升:考虑到我们已经拥有足够多的样本,足以排除是系统误差带来的干扰.

4.2.1 低队列性能牙膏倒吸?

中文技术社区的实测里,低队列深度场景出现性能下降的反馈非常集中,还有填盘测试出现反常结果的案例,启用原生 NVMe 驱动后速度反而下降,用户直观感受是之前填盘快是在未启用原生驱动的情况下,换新驱动后表现更差

技术解释并不复杂,多队列与深队列的优势往往在 QD≥32 的高并发提交下才会显现,而普通用户超过九成的 I/O 操作都停留在低队列深度,架构改进带来的理论收益触发概率很低;当新路径引入新的调度策略、中断聚合策略或队列亲和性变化时,低 QD 下的额外开销更容易放大成体感差异

4.2.2 顺序读写的提升往往接近不可感知

AS SSD 基准测试里,有用户实测平均读取提升约 9%,写入提升约 19%,评价是日常几乎感知不到;TechPowerUp 的用户反馈更直接,基准只略微更快,NVMe 本身已经足够快,额外 3% 的提升意义不大

这类反馈说明顺序带宽在很多桌面场景早就不是瓶颈,真正影响体验的往往是尾延迟、稳定性这些指标 (同时也是 SSD 和软件栈厂商不愿提及的指标!)

4.2.3 多线程随机 I/O 的提升有限,还可能带来 CPU 侧副作用

知乎上有人测了,4K 随机读在 1 到 8 线程测试中,新旧路径互有胜负,同时在 T4 与 T8 线程时 CPU 占用更高,IOPS 增益被 CPU 开销抵消。这和微软官方宣传口径形成对照,微软宣称的 80% IOPS 提升多基于 DiskSpd.exe 的高队列深度压测,典型是 QD=32 以上并叠加多线程,属于对存储栈最友好的实验条件,但与真实工作负载差异真的很大

这里贴一张我自己的 CDM 实测(平台:i7-14700KF + 128G RAM + ASUS ROG Strix B760G 小吹雪)(待测盘是 WD SN5000 1T,健康度 100%,保持 50% 半满状态,且非系统盘),成绩仅供娱乐,没有啥参考价值,我也不做任何评价:

开启原生 NVMe 前:

开启原生 NVMe 后:

这个跑分成绩仅供娱乐,没有啥参考价值,我也不做任何评价

4.3 为什么会这样?瓶颈可能不在 NVMe

即便 NVMe 设备与驱动路径表现理想,系统端到端吞吐也未必提升,应用程序 I/O 设计很重要,多数消费级软件并没有围绕多队列并行做专门优化,很难持续触发原生路径的优势

而且为了追求更亮眼的跑分成绩,过去十多年 Windows 的 SCSI 到 NVMe 翻译路径应该是得到了针对性的持续优化的,在常规负载下效率损失已被压的很低,原生路径的提升空间本来就没想象中大

消费级 SSD 主控的设计取向可能也影响巨大,多数消费级 NVMe SSD 并没有围绕极深队列和多队列并行做优化。如果硬件侧不配合,那么软件路径的理论优势很难转化为可见收益