我有一个定义为typedef unsigned char uint40[5]
的类型,然后我有一个uint40数组,比如uint40* payloads
我试图将以下函数移植到CUDA内核
void aSimpleFunction(int M, uint40* data)
{
for (auto i = 0; i < M; i++)
{
unsigned int* dataPtr = (unsigned int*)data[i];
*dataPtr = 2158677232;
data[i][4] = 1;
}
}
对我来说这很简单,但它没有用。但是,使用方括号来访问它确实有效的每个元素。
__global__ void aSimpleKernel(int M, uint40* data)
{
int tid = threadIdx.x + 1;
// DOESN'T WORK
unsigned int* dataPtr = (unsigned int*)data[tid];
*dataPtr = 16976944;
// WORKS
/*
data[threadIdx.x][0] = tid * 1;
data[threadIdx.x][1] = tid * 2;
data[threadIdx.x][2] = tid * 3;
data[threadIdx.x][3] = tid * 4;
*/
data[threadIdx.x][4] = 2;
}
是否可以将char *转换为CUDA内核中的unsigned int *?
由于&#34;没有工作&#34;我的意思是,它具有随机数而不是我在打印uint40 *数组的每个元素时所期望的数字。有时候,GPU显然会崩溃,因为Windows中有一个弹出窗口告诉我gpu已成功重启。
答案 0 :(得分:5)
如果您在使用CUDA代码时遇到问题,最好使用proper cuda error checking并使用cuda-memcheck
运行代码。即使你不理解错误输出,它对那些试图帮助你的人也很有用,所以我建议在之前在这里寻求帮助。
我试图从你所展示的内容中制作完整的代码是这样的:
#include <stdio.h>
typedef unsigned char uint40[5];
void aSimpleFunction(int M, uint40* data)
{
for (int i = 0; i < M; i++)
{
unsigned int* dataPtr = (unsigned int*)data[i];
*dataPtr = 0x02020202U;
data[i][4] = 1;
}
}
void uint40_print(uint40 &data){
char *my_data = (char *)&data;
for (int i = 0; i < 5; i++) printf("%d", my_data[i]);
printf("\n");
}
__global__ void aSimpleKernel(int M, uint40* data)
{
for (int i = 0; i < M; i++)
{
unsigned int* dataPtr = (unsigned int*)data[i];
printf("%p\n", dataPtr);
*dataPtr = 0x02020202U;
data[i][4] = 1;
}
}
int main(){
uint40 *payloads = (uint40 *)malloc(10000);
memset(payloads, 0, 10000);
aSimpleFunction(5, payloads);
uint40_print(payloads[0]);
memset(payloads, 0, 10000);
uint40 *d_payloads;
cudaMalloc(&d_payloads, 10000);
aSimpleKernel<<<1,1>>>(5, d_payloads);
cudaMemcpy(payloads, d_payloads, 10000, cudaMemcpyDeviceToHost);
for (int i = 0; i < 5; i++) uint40_print(payloads[i]);
return 0;
}
当我编译并运行该代码时,我得到如下输出:
$ ./t1091
22221
00000
$
果然,GPU输出与CPU输出不匹配。如果我使用cuda-memcheck
运行代码,我得到的输出的一部分如下所示:
$ cuda-memcheck ./t1091
========= CUDA-MEMCHECK
22221
========= Invalid __global__ write of size 4
========= at 0x00000080 in /home/bob/misc/t1091.cu:28:aSimpleKernel(int, unsigned char[5]*)
========= by thread (0,0,0) in block (0,0,0)
========= Address 0x402500005 is misaligned
这给出了实际问题的线索。实际上,您正在创建一个char
数组,然后在其上叠加一个5字节宽的结构(uint40)。这意味着连续的uint40
项将从相差5的字节地址开始。
当您使用其中一个地址并将其转换为int
或unsigned int
指针时,最终可能会出现未对齐的指针。 CUDA要求在naturally aligned boundaries上进行POD数据类型的所有访问。因此,必须在4字节边界(0,4,8,...)上访问32位数量(例如int
,float
等)。 uint40
(0,5,10,...)的许多5字节边界也不属于4字节边界,因此尝试以这种方式访问4字节数量是非法的
这个特定用法示例的一个可能的解决方案,并假设您传递给内核的指针是由cudaMalloc
(用于对齐)返回的指针,只是为了改变你的typedef:
typedef unsigned char uint40[8];
这会强制每个uint40
项落在8字节边界上,这也是一个4字节边界。这样做的副作用是分配每8个未使用的字节。
在您的情况下,您表示uint40
类型是数据的集合,而不是单个数字量,因此它实际上是数据&#34;结构&#34;碰巧每个元素占用5个字节。一系列这样的&#34;结构&#34;将有效地作为AoS(结构数组)存储格式,并且对这种数据的性能的共同转换是将其转换为SoA(数组结构)存储格式。因此,另一种可能的方法是创建两个数组:
typedef unsigned char uint40a[4];
typedef unsigned char uint40b[1];
uint40a *data1;
uint40b *data2;
cudaMalloc(&data1, size);
cudaMalloc(&data2, size);
以这种方式访问您的数据。与5字节结构相比,这将保持存储密度,几乎可以肯定地提供对GPU中数据的更快访问。
如果从上面有任何疑问,你就不能选择任意char
指针,将其转换为另一个(更大)数据类型,并期望好事发生。您使用的指针必须与所引用的数据类型正确对齐。