Использование cudamalloc ().Почему двойной указатель? - PullRequest
35 голосов
/ 03 ноября 2011

В настоящее время я изучаю учебные примеры по http://code.google.com/p/stanford-cs193g-sp2010/ для изучения CUDA. Код, который демонстрирует функции __global__, приведен ниже. Он просто создает два массива, один на CPU и один на GPU, заполняет массив GPU числом 7 и копирует данные массива 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**)&device_array, num_bytes); с двойным указателем? Даже здесь определение cudamalloc () on говорит о том, что первый аргумент является двойным указателем.

Почему бы просто не вернуть указатель на начало выделенной памяти на GPU, как это делает функция malloc на CPU?

Ответы [ 5 ]

20 голосов
/ 03 ноября 2011

Все функции API CUDA возвращают код ошибки (или cudaSuccess, если ошибки не произошло).Все остальные параметры передаются по ссылке.Однако в простом C вы не можете иметь ссылки, поэтому вам нужно передать адрес переменной, которую вы хотите, чтобы информация о возвращении была сохранена.Поскольку вы возвращаете указатель, вам необходимо передать двойной указатель.

Другая известная функция, которая работает с адресами по той же причине, - это функция scanf.Сколько раз вы забыли написать это & перед переменной, в которой вы хотите сохранить значение?;)

int i;
scanf("%d",&i);
18 голосов
/ 03 ноября 2011

Это просто ужасный, ужасный дизайн API. Проблема с передачей двойных указателей для функции выделения, которая получает абстрактную (void *) память, состоит в том, что вам нужно создать временную переменную типа void * для хранения результата, а затем присвоить ее действительному указателю правильного типа. Вы хотите использовать. Приведение, как в (void**)&device_array, является недопустимым C и приводит к неопределенному поведению. Вы должны просто написать функцию-обертку, которая ведет себя как обычно malloc и возвращает указатель, например:

void *fixed_cudaMalloc(size_t len)
{
    void *p;
    if (cudaMalloc(&p, len) == success_code) return p;
    return 0;
}
7 голосов
/ 03 ноября 2011

Мы приводим его к двойному указателю, потому что это указатель на указатель.Он должен указывать на указатель памяти GPU.Что делает cudaMalloc (), так это то, что он выделяет указатель памяти (с пробелом) на GPU, который затем указывается первым аргументом, который мы передаем.

2 голосов
/ 31 октября 2017

В C / C ++ вы можете динамически выделять блок памяти во время выполнения, вызывая функцию malloc.

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

Функция malloc возвращает адрес выделенного блока памяти, который может быть сохранен в переменной какого-либо указателя.
Распределение памяти в CUDA немного отличается в двух отношениях,

  1. cudamalloc возвращает целое число в качестве кода ошибки вместо указатель на блок памяти.
  2. В дополнение к размеру байта выделенный, cudamalloc также требует двойной указатель void в качестве его первый параметр.

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

Причина первого различия состоит в том, что все функции API CUDA следуют соглашению о возврате целочисленного кода ошибки. Таким образом, для обеспечения согласованности API cudamalloc также возвращает целое число.

Требования к двойному указателю в качестве первого аргумента функции можно понять в два этапа.

Во-первых, поскольку мы уже решили сделать так, чтобы cudamalloc возвращал целочисленное значение, мы больше не можем использовать его для возврата адреса выделенной памяти. В Си единственный способ взаимодействия функции - это передача указателя или адреса функции. Функция может вносить изменения в значение, хранящееся по адресу или адресу, на который указывает указатель. Изменения этих значений могут быть впоследствии извлечены за пределы области действия функции с использованием того же адреса памяти.

как работает двойной указатель

Следующая диаграмма иллюстрирует, как это работает с двойным указателем.

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

enter image description here

Зачем нам нужен двойной указатель? Почему это работает

Обычно я живу в мире питонов, поэтому я также пытался понять, почему это не сработает.

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
}

enter image description here

1 голос
/ 03 ноября 2011

Проблема: вы должны вернуть два значения: код возврата и указатель на память (если код возврата указывает на успех).Таким образом, вы должны сделать один из них указателем на тип возвращаемого значения.И в качестве типа возврата у вас есть выбор между указателем возврата на int (для кода ошибки) или указателем возврата на указатель (для адреса памяти).Там одно решение так же хорошо, как и другое (и одно из них дает указатель на указатель (я предпочитаю использовать этот термин вместо двойной указатель , так как это звучит больше как указатель на двойное число с плавающей запятой))).

В malloc у вас есть замечательное свойство, которое может содержать нулевые указатели для обозначения ошибки, поэтому вам в основном нужно только одно возвращаемое значение. Я не уверен, возможно ли это с указателем на устройствопамяти, так как это может быть из-за отсутствия или неправильного значения NULL (помните: это CUDA и NOT Ansi C).Возможно, нулевой указатель на хост-системе полностью отличается от нулевого, используемого для устройства, и поэтому возвращение нулевого указателя для указания ошибок не работает, и вы должны сделать API таким образом (это также означаетчто у вас НЕТ общего NULL на обоих устройствах).

...