PCIe 性能调优
带宽是"跑出来的"——从 TLP 开销、链路参数到中断布局
为什么"标称带宽"永远跑不满?
Gen5 x16 标称 63 GB/s,但应用层吞吐往往只有一半——差距来自协议开销:每个 TLP 都有 3DW/4DW 头(12/16B)+ LCRC(4B);读操作要为 CplD 多付一遍包头;MRRS 把一次大读拆成多个 Completion;ASPM 退出、信用耗尽、中断亲和错位都在偷带宽。本页按"包内开销 → 链路参数 → 属性/并发 → 低功耗与中断"四层梳理可调项,全部参数位于 PCIe Capability 的 Device Control(偏移 0x08)与 Link Control(偏移 0x10)。
先量化再动手
调优前先用 带宽与时延计算器的"负载效率"页签量化当前 MPS/MRRS 下的理论效率,避免盲目改参数。任一参数的改动都应用 fio/iperf/netperf 等负载实测验证。
MPS 与 MRRS:包大小的两把旋钮
| 参数 | DevCtl 位段 | 编码 | 作用 |
|---|---|---|---|
| Max_Payload_Size(MPS) | bits[7:5] | 000=128B … 101=4096B | 本设备发出的 TLP(MWr/读到的 CplD)最大负载;值越大包头开销占比越低 |
| Max_Read_Request_Size(MRRS) | bits[14:12] | 同上编码 | 本设备一次 MRd 请求的最大数据量;Completion 会被对端按此拆分 |
MPS 的链路约束
MPS 是端到端约束:路径上任一端点的 TLP 不得超过沿途所有链路段最小的 DevCap.MPS(Device Capabilities bits[7:5])。内核在枚举时统一对齐路径 MPS——pcie_bus_config 的取值决定策略:pci=pcie_bus_perf 按路径能力取最大(默认);pci=pcie_bus_safe 收敛到全系统统一值;pci=pcie_bus_tune_off 使用固件设置的值不动。设备驱动通常不需要也不能自行写 MPS(下游设备的 MPS 由 core 对齐),但 MRRS 是设备自身旋钮,驱动可写。
# 读取当前 DevCtl(PCIe Cap + 0x08)
lspci -vvv -s 01:00.0 | grep -A2 "DevCtl:"
# 或 setpci 直接读(PCIe Cap 偏移需先从 0x34/0x40 能力链定位)
setpci -s 01:00.0 CAP_EXP+08.w
# 内核级 MPS 策略(推荐路径)
# GRUB: pci=pcie_bus_perf → 按路径能力最大化 MPS
# pci=pcie_bus_safe → 全系统统一 MPS(兼容性优先)
写效率 vs 读效率:不对称的开销
| 操作 | 开销构成 | 效率特征 |
|---|---|---|
| 写(MWr) | 每包一次:4DW 头 + 负载 + LCRC | Posted 事务,连续大写接近满效率 |
| 读(MRd + CplD) | 请求 4DW 头;每个 CplD 再付 3DW/4DW 头 | 小 MRRS 时 Completion 头开销被放大——这就是 MRRS 对读性能敏感、对写无关的原因 |
例:MRRS=128B 读 1KB 需要 8 个 CplD,8 × 20B 头 ≈ 160B 纯开销;MRRS=512B 时开销降到约 40B。用 带宽计算器的"传输时延"页签可以把这笔账算到微秒级。
RCB:读完成边界
Read Completion Boundary(RCB)位于 Link Control bits[3](仅根口有意义):约定 Completion 从哪个边界对齐拆分——0 = 64B,1 = 128B。它决定 Switch/EP 在返回读数据时如何对齐拆包;与系统 Cache Line 大小匹配(x86 通常 64B)可避免"跨行 Completion 导致的多余包"。默认 64B;仅当确认全路径 RCB 支持与 Cache Line 对齐收益时才置 1,属于微调项。
TLP 属性:Relaxed Ordering 与 No Snoop
| 属性 | DevCtl 位 | 语义 | 适用判断 |
|---|---|---|---|
| Enable Relaxed Ordering | bit4 | 允许设备把非 posted 写标记为 RO(宽松排序),可越过先前 posted 写先行提交 | 对"写完成顺序不敏感"的流(如批量数据填充)有益;x86 平台收益有限(其内存系统本就近似强序),部分 SoC/直通场景有明确收益 |
| Enable No Snoop | bit11 | 标记 TLP 可豁免缓存一致性 snooping | 仅当数据缓冲区确定不在 CPU 缓存中(设备私有内存/持久映射)才有意义;乱开会在缓存里留下过期数据——典型受益者是网卡大块收包缓冲(配合 DDIO 语义则需另评估) |
两者都是端到端属性:中途回报了不支持属性的 Completion 会被判为错误。开启前核对路径上所有设备的能力位(DevCap 的 RO/No Snoop 支持位)。
Tag 管理与并发深度
读性能的另一半来自并发:MRd 用 Tag 区分在途请求,Tag 空间决定同一时刻最多有多少个未完成读——
- 7-bit 基础 Tag:默认 256 个 Tag;Extended Tag Field Enable(DevCtl bit8)扩展到 8-bit(256 个)。扩展 Tag 要求全路径支持(DevCap.Extended Tag Field Supported)。
- Phantom Functions(DevCtl bit9):用不存在的 Function 号复制 Tag 空间,进一步翻倍在途请求数;同样需要全路径支持且极少数驱动使用。
- 真正的天花板是 NP 信用:对端 NP 信道(Hdr/Data)流量控制信用限制了在途读请求总量——先用 流控信用仿真器直观感受"信用耗尽 → 发送停滞",再看
lspci -vvv里对端 NP 信用值,就知道并发深度该设多大。 - 软件侧对应"轮询/中断合并/多队列深度":队列深度 × 单队列在途请求数应贴近(不超过)NP 信用上限。
ASPM 与中断布局
- ASPM 的性能税:L0s/L1 退出延迟见 电源管理页。低时延路径(NVMe 队列中断)上 ASPM L1 可能带来可测的尾延迟——内核策略
pcie_aspm.policy与设备级echo 0 > .../link/l1_aspm(按固件暴露)可逐点权衡;高吞吐批处理负载则可放心保留 ASPM 省电。 - MSI-X 亲和性:把每个队列的中断固定到收包核(
irq_set_affinity_hint+ irqbalance 排除),避免跨核搬运;多队列设备的核-队列映射见 设备驱动页 MSI 硬件层一节。 - 中断聚合:中断 Moderation(网卡)或 NVMe 的中断合并阈值,本质是用时延换吞吐,与 MPS/MRRS 同属"先量测再调"的参数。
调优清单(按收益排序)
| 优先级 | 动作 | 预期收益 |
|---|---|---|
| 1 | 确认 MPS 按路径最大化(pci=pcie_bus_perf),MRRS 提到 MPS 或 512B | 读吞吐显著提升(Completion 头开销下降) |
| 2 | MSI-X 多队列 + 中断亲和性对齐 | 消除跨核搬运与单核中断瓶颈 |
| 3 | 对齐队列深度与 NP 信用、开启 Extended Tag | 提高读并发深度 |
| 4 | 按负载评估 ASPM 策略 | 尾延迟 vs 功耗权衡 |
| 5 | 评估 RCB/RO/No Snoop | 平台相关的微调收益 |