PCIe 传输效率曲线:从小包到大块
基于 RTX 5090 (PCIe Gen 5 x16) 实测。展示 1 KB → 1 GB 区间 PCIe H2D/D2H 的带宽爬升曲线,揭示”多大才够”的效率拐点。
1. 为什么关心小包传输
来自 Kernel Launch 开销测量 的结论:单次 launch ~2.6 μs。但如果你的 H2D 传输只有 1 KB,耗时 ~11 μs——两者合计 ~14 μs,而 1 KB 的计算可能只需几十 ns。
理解传输效率与大小的关系,是决定”批量还是流式”的核心依据。
2. 测试程序
cat > pcie_micro.cu << 'EOF'
#include <cuda_runtime.h>
#include <stdio.h>
#define CHECK(c) do { \
cudaError_t r = c; \
if (r != cudaSuccess) { \
printf("Error: %s\n", cudaGetErrorString(r)); \
exit(1); \
} \
} while(0)
int main() {
const size_t sizes[] = {
1024, // 1 KB
4 * 1024, // 4 KB
16 * 1024, // 16 KB
64 * 1024, // 64 KB
256 * 1024, // 256 KB
512 * 1024, // 512 KB
1024 * 1024 // 1 MB
};
const int n = 7;
float *h, *d;
cudaEvent_t s, e;
float t;
CHECK(cudaMallocHost(&h, 2 * 1024 * 1024));
CHECK(cudaMalloc(&d, 2 * 1024 * 1024));
printf("%-10s | %-12s | %-12s | %-12s\n",
"Size", "H2D (GB/s)", "D2H (GB/s)", "Lat (us)");
printf("-----------|--------------|--------------|-------------\n");
for (int i = 0; i < n; i++) {
size_t sz = sizes[i];
int reps = sz < 65536 ? 100000 :
sz < 262144 ? 10000 :
sz < 1048576 ? 5000 : 2000;
cudaEventCreate(&s);
cudaEventCreate(&e);
// H2D bandwidth
cudaEventRecord(s, 0);
for (int j = 0; j < reps; j++)
CHECK(cudaMemcpy(d, h, sz, cudaMemcpyHostToDevice));
cudaEventRecord(e, 0);
cudaEventSynchronize(e);
cudaEventElapsedTime(&t, s, e);
float h2d = (sz * reps / (t / 1000.0))
/ (1024.0 * 1024.0 * 1024.0);
// D2H bandwidth
cudaEventRecord(s, 0);
for (int j = 0; j < reps; j++)
CHECK(cudaMemcpy(h, d, sz, cudaMemcpyDeviceToHost));
cudaEventRecord(e, 0);
cudaEventSynchronize(e);
cudaEventElapsedTime(&t, s, e);
float d2h = (sz * reps / (t / 1000.0))
/ (1024.0 * 1024.0 * 1024.0);
// Single transfer latency
cudaEventRecord(s, 0);
CHECK(cudaMemcpy(d, h, sz, cudaMemcpyHostToDevice));
cudaEventRecord(e, 0);
cudaEventSynchronize(e);
cudaEventElapsedTime(&t, s, e);
float lat = t * 1000;
char b[16];
if (sz >= 1048576)
snprintf(b, 16, "%lu MB", sz / 1048576);
else if (sz >= 1024)
snprintf(b, 16, "%lu KB", sz / 1024);
printf("%-10s | %-12.2f | %-12.2f | %-12.1f\n",
b, h2d, d2h, lat);
cudaEventDestroy(s);
cudaEventDestroy(e);
}
CHECK(cudaFreeHost(h));
CHECK(cudaFree(d));
return 0;
}
EOF
nvcc -o pcie_micro pcie_micro.cu
./pcie_micro
3. RTX 5090 实测曲线
Size | H2D (GB/s) | D2H (GB/s) | Lat (us)
-----------|--------------|--------------|-------------
1 KB | 0.16 | 0.17 | 11.1
4 KB | 0.61 | 0.68 | 16.4
16 KB | 2.08 | 2.58 | 12.2
64 KB | 7.93 | 8.95 | 10.0 ← 最低延迟
256 KB | 21.90 | 24.49 | 12.6
512 KB | 30.84 | 33.56 | 19.4
1 MB | 38.91 | 40.74 | 28.8
3.1 完整曲线(1 KB - 1 GB)
1 KB - 1 MB 来自本节微传输测试(多次迭代取均值),16 MB - 1 GB 来自 PCIe 链路状态与带宽实测(单次大块传输)。两者合并形成完整带宽曲线。
Size | H2D (GB/s) | D2H (GB/s) | Lat (us)
-----------|--------------|--------------|-------------
1 KB | 0.16 | 0.17 | 11.1
4 KB | 0.61 | 0.68 | 16.4
16 KB | 2.08 | 2.58 | 12.2
64 KB | 7.93 | 8.95 | 10.0
256 KB | 21.90 | 24.49 | 12.6
512 KB | 30.84 | 33.56 | 19.4
1 MB | 38.91 | 40.74 | 28.8
16 MB | 50.70 | 52.09 | — ← 接近饱和
64 MB | 52.09 | 52.92 | — ← 饱和
1 GB | 52.50 | 53.34 | — ← 峰值
4. 效率拐点分析
4.1 带宽效率 (% of peak 52.5 GB/s)
| 大小 | H2D 效率 | 瓶颈 |
|---|---|---|
| 1 KB | 0.3% | PCIe TLP header 开销 (~28B per TLP) 占比极大 |
| 4 KB | 1.2% | 仍有大量 header 开销 |
| 16 KB | 4.0% | — |
| 64 KB | 15.1% | 延迟最低点 (10.0 μs) |
| 256 KB | 41.7% | — |
| 1 MB | 74.1% | — |
| 16 MB | 96.6% | 接近饱和 |
4.2 三阶段模型
这个曲线反映了 PCIe 传输的两个本质瓶颈:
- 固定开销(~10 μs):每次
cudaMemcpy都需要 TLP 打包、PCIe 链路仲裁、ACK 返回——这些开销与传输大小无关,是纯 latency 成本。 - 带宽开销(~19 ns/B at Gen 5 ×16):一旦传输开始,数据以 ~52.5 GB/s 的速率流动。这是纯 throughput 成本。
总时间 = 固定开销 + 数据量 / 带宽。小传输时固定开销主导,大传输时带宽主导。
阶段 1: 延迟主导 (1-64 KB)
📉 带宽从 0.16 → 8 GB/s
📍 每次传输 ~10 μs 固定开销(TLP 打包 + PCIe 链路往返)
💡 启示:拼接多个小传输为一个批量传输
阶段 2: 混合 (64 KB - 16 MB)
📈 带宽从 8 → 50 GB/s
📍 传输时间开始主导,但还未完全饱和 PCIe 管道
💡 启示:256 KB+ 已可接受,1 MB+ 效率 > 74%
阶段 3: 饱和 (16 MB+)
📊 带宽 > 50 GB/s,接近理论极限 (~63 GB/s)
📍 PCIe 管道填满,效率 > 95%
💡 启示:16 MB+ 无需优化,已达硬件极限
5. 实用建议
| 场景 | 建议最小传输大小 | 原因 |
|---|---|---|
| 超低延迟推理 | 64 KB | 延迟最低 (10 μs) |
| 高吞吐训练 | 16 MB+ | 带宽 > 96% |
| 折中方案 | 256 KB - 1 MB | 带宽 42-74%,延迟可接受 |
| 避免 | < 4 KB | 效率 < 2%,浪费 PCIe 带宽 |
6. 与 Kernel 开销联动
从 Kernel Launch 开销:单次 launch ~2.6 μs。
总开销 = launch + PCIe 传输:
| 计算量 | 最优执行位置 | 原因 |
|---|---|---|
| < 10 μs | CPU | launch + H2D + D2H > 计算本身 |
| 10-100 μs | GPU (批量) | CPU 做 H2D batching |
| > 100 μs | GPU | 计算时间主导,GPU 加速明显 |