CUDA中cudaGetSymbolAddress拷贝嵌套结构体的用法疑问及替代方案
主机和设备端各存储了一份嵌套数据结构,需要将主机端的某个内部字段拷贝到设备端对应位置。已知所有内存分配操作均正确完成,尝试通过内核获取设备端最内层成员的地址并存储到__device__变量d2,再用cudaGetSymbolAddress将该地址读取到主机端dummy变量,最后通过cudaMemcpy完成拷贝,但调试发现d2和dummy存储的地址不一致,方案无法正常工作。
另外,由于主机端无法解引用设备指针d_vd,无法直接使用cudaMemcpy拷贝嵌套字段,例如以下代码无法运行:
cudaMemcpy(&(d_vd[c]->last_insert_block->last_insert_offset), &(vd[k]->last_insert_block->last_insert_offset), sizeof(int), cudaMemcpyHostToDevice);
现有错误代码片段:
__device__ int* d2; __global__ void getLIO(EdgeSentinel** d_vd, unsigned int c) { d2 = &(d_vd[c]->last_insert_block->last_insert_offset); printf("d2 contains = %p\n", d2); } int main() { // 假设d_vd(设备端)和vd(主机端)的结构分配均正确 // 大致结构:vd包含last_insert_block指针,指向另一个包含int类型last_insert_offset字段的结构 unsigned int c, k; // 假设c和k已被初始化为有效值 getLIO<<<1,1>>>(d_vd, c); cudaDeviceSynchronize(); int* dummy; cudaGetSymbolAddress((void**)&dummy, d2); printf("Dummy contains = %p\n", dummy); cudaMemcpy(dummy, &(vd[k]->last_insert_block->last_insert_offset), sizeof(int), cudaMemcpyHostToDevice); }
1. cudaGetSymbolAddress的错误原因
cudaGetSymbolAddress的作用是获取__device__全局变量本身在设备内存中的地址,而非该变量存储的内容。你当前调用它拿到的是d2这个变量自身的地址,不是d2里面存储的内层字段地址,这就是两者不一致的核心原因。
2. 可行替代方案
方案一:内核直接完成拷贝
既然内核可以访问设备端目标字段,直接在内核中接收主机端数据并完成赋值,无需额外传递地址:
__global__ void copyLIO(EdgeSentinel** d_vd, unsigned int c, int host_value) { d_vd[c]->last_insert_block->last_insert_offset = host_value; } int main() { // ... 其他初始化代码 ... int host_val = vd[k]->last_insert_block->last_insert_offset; copyLIO<<<1,1>>>(d_vd, c, host_val); cudaDeviceSynchronize(); }
该方案最直接,内核直接操作设备内存完成赋值,省去额外的cudaMemcpy步骤。
方案二:通过设备内存传递目标地址
如果一定要使用cudaMemcpy,可以用cudaMalloc分配一块设备内存存储目标地址,再拷贝回主机端,替代__device__全局变量:
int main() { // ... 其他初始化代码 ... // 分配设备内存存储目标地址 int** d_target_addr; cudaMalloc(&d_target_addr, sizeof(int*)); // 内核将目标地址写入设备内存 getLIO<<<1,1>>>(d_vd, c, d_target_addr); cudaDeviceSynchronize(); // 将设备端的目标地址拷贝到主机端 int* dummy; cudaMemcpy(&dummy, d_target_addr, sizeof(int*), cudaMemcpyDeviceToHost); // 执行字段拷贝 cudaMemcpy(dummy, &(vd[k]->last_insert_block->last_insert_offset), sizeof(int), cudaMemcpyHostToDevice); // 释放临时内存 cudaFree(d_target_addr); } // 修改后的内核 __global__ void getLIO(EdgeSentinel** d_vd, unsigned int c, int** d_target_addr) { *d_target_addr = &(d_vd[c]->last_insert_block->last_insert_offset); printf("Target address = %p\n", *d_target_addr); }
通过cudaMalloc分配的设备内存传递地址,再用cudaMemcpy将地址值读取到主机端,就能正确获取设备端字段的地址,后续cudaMemcpy可正常工作。
方案三:计算字段偏移量直接寻址
如果嵌套结构在主机和设备端的内存布局一致(默认CUDA结构布局与主机相同,除非有特殊编译选项),可以计算目标字段相对于结构起始地址的偏移量,结合设备端结构的起始地址得到目标地址:
// 假设BlockStruct是last_insert_block指向的结构体类型 size_t offset = offsetof(EdgeSentinel, last_insert_block) + offsetof(BlockStruct, last_insert_offset); // 计算设备端目标地址 int* dummy = (int*)((char*)d_vd[c] + offset); // 执行拷贝 cudaMemcpy(dummy, &(vd[k]->last_insert_block->last_insert_offset), sizeof(int), cudaMemcpyHostToDevice);
该方案无需启动内核,纯主机端计算完成,效率更高,前提是要确保结构布局一致,且正确定义所有嵌套结构体类型。
内容的提问来源于stack exchange,提问作者Programmer

