在CUDA中将char *转换为unsigned int *

时间:2016-02-26 21:34:45

标签: cuda

我有一个定义为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已成功重启。

1 个答案:

答案 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的字节地址开始。

当您使用其中一个地址并将其转换为intunsigned int指针时,最终可能会出现未对齐的指针。 CUDA要求在naturally aligned boundaries上进行POD数据类型的所有访问。因此,必须在4字节边界(0,4,8,...)上访问32位数量(例如intfloat等)。 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指针,将其转换为另一个(更大)数据类型,并期望好事发生。您使用的指针必须与所引用的数据类型正确对齐。