3、内存拷贝的硬件路径:PCIe总线、DMA引擎、GPU内部互联
好,咱们接着聊。上一节我们搞清楚了内存拷贝到底在拷什么——说白了就是数据在物理内存地址之间的搬运。那这一节,我们来扒一扒这些数据到底是怎么从CPU那边跑到GPU这边的。嗯,这条路可不简单。
我个人习惯把内存拷贝的硬件路径比作一条「物流专线」。你想想看,数据就像包裹,从CPU的仓库(系统内存)出发,经过中转站(PCIe总线),再由专门的快递车队(DMA引擎)负责运输,最后进入GPU内部的各个分拣中心(内部互联网络)。任何一个环节堵了,你的程序就得等着。
3.1 PCIe总线:数据搬运的主干道
PCIe总线,说白了就是CPU和GPU之间的那条高速公路。我刚开始做CUDA优化时,总觉得PCIe带宽是瓶颈,后来发现——嗯,它确实是瓶颈,但很多人连瓶颈在哪都没搞清楚。
PCIe有几个关键参数你得记住:
| PCIe版本 | 单通道带宽(x1) | x16带宽(单向) | 常见场景 |
|---|---|---|---|
| PCIe 3.0 | ~1 GB/s | ~16 GB/s | 老显卡、服务器 |
| PCIe 4.0 | ~2 GB/s | ~32 GB/s | RTX 30系、AMD RX 6000 |
| PCIe 5.0 | ~4 GB/s | ~64 GB/s | RTX 40系、最新工作站 |
这里有个坑,我曾经踩过。有一次我在项目里用了一张RTX 3090,理论带宽应该是PCIe 4.0 x16,结果跑出来的拷贝速度只有12 GB/s。查了半天,发现主板只支持PCIe 3.0,显卡插上去自动降级了。所以啊,别光看显卡参数,主板和CPU也得匹配。
nvidia-smi topo -m 可以查看实际链路信息。
3.2 DMA引擎:真正的搬运工
很多人以为cudaMemcpy是CPU在干活,其实不是。CPU只是下个指令,真正干活的是DMA引擎。
DMA,全称Direct Memory Access,直接内存访问。它是个硬件模块,可以绕过CPU,直接在系统内存和GPU显存之间搬数据。为什么要绕过CPU?因为CPU忙着算东西呢,你让它去搬砖,太浪费了。
我给你们画个流程图,看看DMA是怎么工作的:
你看,CPU只负责下指令,然后就可以去干别的事了。DMA引擎自己把数据从系统内存搬到显存,搬完了再通知GPU:「嘿,数据到了,你可以开始算了。」这就是异步的本质。
3.3 GPU内部互联:数据进门的最后一公里
数据到了GPU这边,还没完。它得从显存分配到具体的SM(流多处理器)上去。这里涉及GPU内部的互联网络。
NVIDIA的GPU内部用的是NVLink或者PCIe Switch的拓扑结构。我给你们列一下常见的互联方式:
- 单GPU:数据从PCIe控制器 → L2缓存 → L1缓存 → 寄存器。路径最短,延迟最低。
- 多GPU(NVLink):GPU之间可以直接通过NVLink通信,不用走PCIe。速度能到600 GB/s以上,比PCIe快一个数量级。
- 多GPU(PCIe Switch):通过PCIe Switch互联,走的是PCIe协议,带宽受限,但兼容性好。
我记得有一次做多卡训练,发现卡间通信特别慢。查了拓扑才发现,两张卡虽然插在同一台机器上,但走的是不同的PCIe Root Complex,数据得绕到CPU那边再回来。后来换了支持NVLink的卡,速度直接起飞。
nvidia-smi topo -m 可以查看GPU之间的拓扑关系。如果看到 "NV" 字样,说明有NVLink直连;如果是 "PIX" 或 "PHB",那就得走PCIe了。
3.4 实战:如何选择最优的拷贝路径
说了这么多,到底怎么用?我给你几个实际建议:
- 能一次拷完就别分多次。DMA启动有开销,一次拷1 GB比100次拷10 MB快得多。
- 用固定内存(Pinned Memory)。系统内存默认是分页的,DMA不能直接访问。用cudaHostAlloc分配固定内存,DMA就能直接搬,速度翻倍。
- 多流并发。如果你有多个DMA引擎(高端GPU有多个),可以用多个流同时拷贝,充分利用PCIe带宽。
我曾经在一个项目里,把普通的内存分配改成固定内存,再把单流拷贝改成双流并发,拷贝速度从8 GB/s提到了28 GB/s。嗯,效果就是这么明显。
好了,这一节我们走完了数据从CPU到GPU的完整硬件路径。下一节我们会聊聊如何用工具把这些路径上的性能瓶颈抓出来。记住一句话:理解了硬件路径,你才能写出真正高效的代码。
想要更多资料,联系可可