文章目录
-
-
- [1. 最小化传输数据量](#1. 最小化传输数据量)
- [2. 使用固定内存](#2. 使用固定内存)
- [3. 合并小批量传输](#3. 合并小批量传输)
- [4. 数据传输中的序列化技巧](#4. 数据传输中的序列化技巧)
-
- [A. 针对结构体数组:降低访存开销](#A. 针对结构体数组:降低访存开销)
- [B. 针对不规则数据:扁平化处理](#B. 针对不规则数据:扁平化处理)
- [C. 针对二维矩阵:按对角线序列化](#C. 针对二维矩阵:按对角线序列化)
- [5. 高级优化:异步传输与双缓冲](#5. 高级优化:异步传输与双缓冲)
- 总结流程
-
在 CUDA C++ 中处理 CPU 和 GPU 之间的数据传递,核心挑战在于 PCIe 总线的有限带宽 。优化传输的关键在于三个层面: 传输前的数据压缩 、 传输过程中的内存类型选择 以及 序列化策略 以支持高效访问。
以下是针对数据传输与序列化/反序列化的实用技巧总结:
1. 最小化传输数据量
这是最重要的原则。由于 PCIe 带宽远低于 GPU 内部显存带宽,只要有可能,应该在 GPU 上多做计算,仅传输必要的输入和最终结果。
- 技巧 :如果计算逻辑允许,尽量先在 GPU 上对数据进行筛选、规约或预处理,减少了需要回传至 CPU 的数据量。
2. 使用固定内存
默认的 malloc 分配的是可分页内存,GPU 无法直接访问,需要 CPU 做一次临时拷贝。使用固定内存可以提升约 2-5 倍的传输带宽。
- 技巧 :使用
cudaMallocHost替代malloc分配主机内存。这种内存页被锁定,允许 GPU 直接通过 DMA 访问,绕过 CPU 的页面拷贝开销。 - 注意:分配过多的固定内存会降低整个系统的性能,因为它减少了系统可分页的虚拟内存量。
3. 合并小批量传输
每次调用 cudaMemcpy 都有固定的启动延迟。
- 技巧:将许多小的传输合并成一个大的传输。例如,将多个数组打包到一个大的结构体中一次性传输,而不是在循环中逐个传输。
4. 数据传输中的序列化技巧
当处理复杂数据结构时,不能在 GPU 上直接使用 CPU 的指针,因此需要特定的序列化手段。
A. 针对结构体数组:降低访存开销
- 场景:向量加法、简单的结构体处理。
- 技巧 :如果内核仅仅需要对象中的几个特定数据项,考虑使用 "Structure of Arrays" 而不是 "Array of Structures"。
- 代码示例:这种布局能让 GPU 的内存访问更加连续,提高合并访问的效率。
cpp
// 不推荐 (AoS): 内存不连续,读取x时会跳过y
struct Vec3 { float x, y, z; };
Vec3* h_data = (Vec3*)malloc(N * sizeof(Vec3));
// 推荐 (SoA): 连续存储,支持合并访问,适合序列化传输
float* h_x = (float*)malloc(N * sizeof(float));
float* h_y = (float*)malloc(N * sizeof(float));
B. 针对不规则数据:扁平化处理
- 场景:链表、树、变长字符串、数据结构含有指针。
- 技巧 :GPU 无法解析 CPU 的虚拟地址。必须将数据扁平化 。
- 不要传输指针,而是传输偏移量。
- 将散乱的数据集中存放在一个大的
char数组(或vector)中。
- 示例 :
- CPU结构 :
Node { int value; Node* next; } - GPU结构 :
GPUNode { int value; int next_index; }
- CPU结构 :
- 传输方式 :将连续内存块
[value1, index1, value2, index2...]一次性拷贝到 GPU。
C. 针对二维矩阵:按对角线序列化
- 场景:动态规划算法(如 Smith-Waterman 基因比对、Pair-HMM)。
- 技巧 :虽然通常按行存储,但如果内核是按斜对角线并行计算的,按斜对角线序列化可以保证 GPU 线程访问的数据在物理上是连续的,大幅提升性能。
- 图示说明:将矩阵旋转 45 度角存储为一维数组,使得单条斜边上的元素在内存中紧挨着。
5. 高级优化:异步传输与双缓冲
- 技巧 :利用
cudaMemcpyAsync和 CUDA 流,使数据传输与内核执行重叠。 - 双缓冲:创建一个"滑动窗口"。当 GPU 处理缓冲区 A 的数据时,CPU 正在准备并将缓冲区 B 的数据拷贝到 GPU。这能最大程度隐藏传输延迟。
总结流程
为了兼顾高效传输 与正确解析,一个标准的工作流如下:
- CPU端 :将复杂数据扁平化/序列化到连续的
std::vector或固定内存缓冲区中。 - 传输 :使用
cudaMemcpy或cudaMemcpyAsync拷贝这个扁平化的缓冲区。 - GPU端:内核接收这个缓冲区的指针,通过计算基址+偏移量来解析数据。
- 返回:将结果以扁平化数组的形式传回,CPU 再进行反序列化重建结构。
通过上述技巧,不仅可以确保程序逻辑正确(避免指针错误),还能显著提升 PCIe 总线的有效吞吐量。