lspci仅显示pcie链路协商状态(lnksta),不反映真实运行带宽;需用nvidia-smi dmon、perf或ethtool等工具实测吞吐,且须排除cpu内存带宽与numa访问瓶颈。

lspci只看协商状态,不测真实带宽
很多人一上来就跑 lspci -vv 看 LnkSta: Speed 16.0GT/s, Width x16,以为这就等于显卡正以满速跑数据——不是。这行只说明链路“协商成功”,不代表GPU和CPU之间真在持续吞吐。真实PCIe传输带宽必须靠运行时测量,lspci 不提供任何计数器或采样接口。
nvidia-smi dmon 是最轻量的实时观测方式
对NVIDIA显卡,nvidia-smi dmon -s puc(p=power, u=util, c=pci)会每秒输出PCIe带宽估算值,单位是MB/s,列名为rx(CPU→GPU)和tx(GPU→CPU):
nvidia-smi dmon -s puc -d 1 -c 5
注意几点:
-
rx/tx是内核通过PCIe AER计数器推算的,非物理层采样,但对多数场景足够反映瓶颈 - 数值受驱动版本影响:470+驱动才稳定支持PCIe计数;老驱动可能全为0或跳变异常
- 必须有实际数据搬运:空载时始终显示0;建议配合
cudaMemcpy循环或nccl-tests触发持续DMA - 若
rx长期卡在远低于理论值(如PCIe 4.0 x16标称≈31 GB/s,却只到8 GB/s),说明不是链路协商问题,而是应用没打满、或被CPU内存带宽/NUMA节点拖累
perf + pci events 能抓底层硬件计数器
要绕过驱动层、直接读取PCIe控制器的硬件性能事件,得用perf(需内核开启CONFIG_PERF_EVENTS_INTEL_UNCORE,AMD平台需CONFIG_AMD_PMC):
Zynq‑7000 / Zynq UltraScale+ 硬件调试技能。适用于 Xilinx Zynq(Cortex‑A9/A53 + FPGA)Linux 环境,调试 I2C、I2S、UART、GPIO 等外设。包括设备树配置、内核驱动、用户空间调试工具、交叉编译和常见问题排查,支持 PetaLinux 开发。
sudo perf stat -e 'pci/*/read-bytes/,pci/*/write-bytes/' -a sleep 5
输出类似:
2,148,924,160 pci//read-bytes/
这个数字是5秒内所有PCIe设备的总读字节数,除以5再除以1024³即得GB/s。关键点:
- 事件名中的
pci/*/read-bytes/是通配符,匹配所有PCIe Root Port;若只想看GPU所在链路,先用lspci -s $(nvidia-smi -q -d PCI | grep "Bus Id" | awk '{print $4}') -vv | grep -A1 LnkSta定位其上游Root Port(如00:01.0),再指定-e 'pci/0000:00:01.0/read-bytes/' - 该计数器反映的是TLP(Transaction Layer Packet)有效载荷字节数,不含协议开销,比
nvidia-smi更接近物理层真实吞吐 - 部分服务器主板(尤其OCP规范网卡直连BMC的)可能不暴露这些PMU事件,
perf list | grep pci为空时就得换方案
别忽略CPU内存子系统这个隐形瓶颈
即使PCIe链路跑满x16@Gen4,GPU仍可能等CPU内存——特别是cudaMallocHost或cudaMemcpy涉及页表映射时。验证方法:
- 用
perf record -e mem-loads,mem-stores -a sleep 5看内存控制器是否饱和 - 检查NUMA拓扑:
numactl --hardware确认GPU插槽所属CPU socket,再用numastat -p $(pgrep python)看进程是否跨NUMA访问内存 - 强制绑定:
numactl -N 0 -m 0 ./your_cuda_app能排除内存路径干扰,此时再测PCIe带宽才具可比性
真正卡在PCIe上的情况其实不多见;更多时候是rx上不去,但perf看到read-bytes很高——说明数据确实在跑,只是GPU没及时消费,问题已移出PCIe层。










