【问题标题】:Use of cudamalloc(). Why the double pointer?使用 cudamalloc()。为什么是双指针?
【发布时间】:2011-12-20 19:15:07
【问题描述】:

我目前正在阅读http://code.google.com/p/stanford-cs193g-sp2010/ 上的教程示例来学习 CUDA。下面给出了演示__global__ 函数的代码。它只是创建了两个数组,一个在 CPU 上,一个在 GPU 上,用数字 7 填充 GPU 数组并将 GPU 数组数据复制到 CPU 数组中。

#include <stdlib.h>
#include <stdio.h>

__global__ void kernel(int *array)
{
  int index = blockIdx.x * blockDim.x + threadIdx.x;

  array[index] = 7;
}

int main(void)
{
  int num_elements = 256;

  int num_bytes = num_elements * sizeof(int);

  // pointers to host & device arrays
  int *device_array = 0;
  int *host_array = 0;

  // malloc a host array
  host_array = (int*)malloc(num_bytes);

  // cudaMalloc a device array
  cudaMalloc((void**)&device_array, num_bytes);

  int block_size = 128;
  int grid_size = num_elements / block_size;

  kernel<<<grid_size,block_size>>>(device_array);

  // download and inspect the result on the host:
  cudaMemcpy(host_array, device_array, num_bytes, cudaMemcpyDeviceToHost);

  // print out the result element by element
  for(int i=0; i < num_elements; ++i)
  {
    printf("%d ", host_array[i]);
  }

  // deallocate memory
  free(host_array);
  cudaFree(device_array);
} 

我的问题是为什么他们用双指针措辞cudaMalloc((void**)&amp;device_array, num_bytes); 语句?甚至 here 上 cudamalloc() 的定义说第一个参数是一个双指针。

为什么不像malloc函数在CPU上那样简单地返回一个指向GPU上分配内存开始的指针?

【问题讨论】:

  • 因为它会返回一个错误代码,告诉您失败的原因。在失败时返回空指针,如 malloc(),是错误代码的不良替代品,仅表示“它不起作用”。你应该检查一下。
  • @Hans:这仍然是一个糟糕的 API 设计。相反,它应该使用一个额外的int *error 参数来存储错误代码,当返回值为空指针时该参数有效。照原样,该设计否定了void 指针的所有好处,并要求您跳过障碍才能正确使用该功能。
  • @R.:您在批评 API 的同时提供了替代方案 - 大多数 API 批评者都没有提出替代方案 - 但除非您认为每个 CUDA 运行时调用都应该采用额外的 int * 传回错误代码,(这会使 API 更加混乱和难以使用),您的替代提案不是正交的,并且违反了最小惊讶原则。
  • 违反最小惊讶原则比要求临时void *到处都是小得多。当用户不关心原因时,int *error 可能为空。实际上,除了“内存不足”之外,我没有看到分配失败的原因(更重要的是,调用者没有理由关心它失败的原因),所以这可能只是一个设计错误。

标签: c cuda malloc


【解决方案1】:

所有 CUDA API 函数都返回一个错误代码(如果没有发生错误,则返回 cudaSuccess)。所有其他参数都通过引用传递。但是,在纯 C 中,您不能有引用,这就是为什么您必须传递您希望存储返回信息的变量的地址。由于要返回指针,因此需要传递双指针。

另一个众所周知的基于相同原因对地址进行操作的函数是scanf 函数。您有多少次忘记在要存储值的变量之前写下这个&amp;? ;)

int i;
scanf("%d",&i);

【讨论】:

  • 是否只需要 CUDA 中的双指针,因为 API 将取消引用指针两次以获取指向存储在设备内存中的数据类型的指针,而另一次获取实际访问内存内容的指针?跨度>
  • 它是必需的,因为函数设置指针。与 C 中的每个输出参数一样,您需要一个指向您设置的实际变量的指针,而不是值本身。
【解决方案2】:

这简直就是一个可怕的、可怕的 API 设计。为获得抽象 (void *) 内存的分配函数传递双指针的问题是,您必须创建一个 void * 类型的临时变量来保存结果,然后将其分配给正确类型的真实指针你想用。转换,如(void**)&amp;device_array,是无效的 C 并导致未定义的行为。您应该简单地编写一个包装函数,其行为类似于正常的malloc 并返回一个指针,如下所示:

void *fixed_cudaMalloc(size_t len)
{
    void *p;
    if (cudaMalloc(&p, len) == success_code) return p;
    return 0;
}

【讨论】:

  • 我相信 CUDART 有一个用于 cudaMalloc() 的模板包装器,这使得 (void **) 强制转换变得不必要。此外,这里给出的功能不是我建议投入生产的东西。它隐藏了 cudaMalloc() 的返回值提供的太多有用信息。
  • 创造一个短语,那就是“简直是一个可怕的、可怕的 API 设计”。最好在各种 API 之间有一个一致的返回值,并传回分配的指针;然后你又回到了我们开始的地方。
  • 那么你将如何返回错误代码和指针呢?请注意,错误处理应该留给 API 的用户,所以它必须返回。
  • @CygnusX1:当然可以。返回值将是失败时的空指针。甚至使用 errorp 参数的唯一原因是如果您想知道 reason 分配失败,但原因总是“没有足够的可用内存”的某种变体,所以它相当没用。通常你不想在 inside if 的正文之前使用原因,例如打印错误消息。
  • 同意,对于这个函数,它可以像那样工作,但对于所有函数来说,NULL 都是错误的。我强烈不同意你关于“愚蠢的一致性”的说法。从长远来看,缺乏一致性会导致混乱。编译器错误消息可以帮助您立即发现错误,但首先遇到这些错误会减慢开发过程,并且不会帮助您阅读现有的代码!人类是有限的,要求他们记住不一致的 API 是行不通的。因为那个,我对你“小心思”吗?我觉得这有点侮辱。
【解决方案3】:

在 C/C++ 中,您可以通过调用 malloc 函数在运行时动态分配一块内存。

int * h_array;
h_array = malloc(sizeof(int));

malloc 函数返回分配的内存块的地址,该地址可以存储在某种指针的变量中。
CUDA 中的内存分配在两个方面有点不同,

  1. cudamalloc 返回一个整数作为错误代码,而不是 指向内存块的指针。
  2. 除了字节大小之外 已分配,cudamalloc 还需要一个双空指针作为其 第一个参数。

    int * d_array cudamalloc((void **) &d_array, sizeof(int))

第一个区别背后的原因是所有 CUDA API 函数都遵循返回整数错误代码的约定。所以为了保持一致,cudamalloc API 也返回一个整数。

函数第一个参数需要双指针,可以分两步理解。

首先,由于我们已经决定让cudamalloc返回一个整数值,我们不能再用它来返回分配内存的地址。在 C 中,函数进行通信的唯一其他方式是将指针或地址传递给函数。该函数可以更改存储在地址或指针指向的地址的值。稍后可以使用相同的内存地址在函数范围之外检索对这些值的更改。

双指针的工作原理

下图说明了它如何使用双指针。

int cudamalloc((void **) &d_array, int type_size) {
  *d_array = malloc(type_size);
  return return_code;
}

为什么我们需要双指针?为什么这行得通

我通常生活在 python 世界中,所以我也很难理解为什么这不起作用。

int cudamalloc((void *) d_array, int type_size) {
  d_array = malloc(type_size);
  ...
  return error_status;
}

那么为什么它不起作用?因为在 C 中,当调用 cudamalloc 时,会创建一个名为 d_array 的局部变量,并为其分配第一个函数参数的值。我们无法在函数范围之外检索该局部变量中的值。这就是为什么我们需要在这里指向一个指针。

int cudamalloc((void *) d_array, int type_size) {
  *d_array = malloc(type_size);
  ...
  return return_code;
}

【讨论】:

    【解决方案4】:

    我们将它转​​换为双指针,因为它是指向指针的指针。它必须指向 GPU 内存的指针。 cudaMalloc() 所做的是它在 GPU 上分配一个内存指针(带空间),然后由我们给出的第一个参数指向。

    【讨论】:

    • 当cudaMalloc的第一个参数需要**时,您的答案用简单的话解释。使用您的回答,我能够理解双指针 API 设计背后的逻辑。现在,第一个取消引用将指向一个指针,该指针实际上指向存储在设备内存中的数据。第二次取消引用实际上会将我指向感兴趣的向量
    【解决方案5】:

    问题:您必须返回两个值:返回代码和指向内存的指针(如果返回代码表示成功)。因此,您必须将其中之一设为返回类型的指针。作为返回类型,您可以选择返回指向 int 的指针(用于错误代码)或返回指向指针的指针(用于内存地址)。有一个解决方案和另一个解决方案一样好(其中一个产生指向指针的指针(我更喜欢使用这个术语而不是 double pointer,因为这听起来更像是一个指向双浮点的指针数))。

    在 malloc 中,您有一个很好的属性,您可以使用空指针来指示错误,因此您基本上只需要一个返回值。我不确定这是否可以使用指向设备内存的指针,因为它可能是没有或错误的 null 值(记住:这是 CUDA 和 NOT Ansi C)。可能是主机系统上的空指针与设备使用的空指针完全不同,因此返回空指针指示错误不起作用,您必须以这种方式制作 API(这也意味着你在两个设备上都没有共同的 NULL)。

    【讨论】:

      猜你喜欢
      • 2012-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-10
      • 2014-12-04
      • 2017-08-09
      • 2010-10-28
      • 2011-05-19
      相关资源
      最近更新 更多