Подтвердить что ты не робот

Почему cudaMalloc() использует указатель на указатель?

Например, cudaMalloc((void**)&device_array, num_bytes);

Этот вопрос уже был , и ответ был "потому что cudaMalloc возвращает код ошибки", но я не понимаю - что двойной указатель связан с возвратом кода ошибки? Почему простой оператор не может выполнить эту работу?

Если я пишу

cudaError_t catch_status;
catch_status = cudaMalloc((void**)&device_array, num_bytes);

код ошибки будет помещен в catch_status, и возврат простого указателя на выделенную память GPU должен быть достаточным, не так ли?

4b9b3361

Ответ 1

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

Когда элемент данных передается функции через список параметров функции, и ожидается, что функция изменит исходный элемент данных, чтобы измененное значение отображалось в вызывающей среде, правильный метод C для этого должен пройти элемент данных по указателю. В C, когда мы проходим по указателю, мы берем адрес элемента, который нужно изменить, создавая указатель (возможно, указатель на указатель в этом случае) и передаем адрес функции. Это позволяет функции изменять исходный элемент (через указатель) в вызывающей среде.

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

Ответ 2

Добавление к Роберту ответа, но сначала повторить, это C API, что означает, что он не поддерживает ссылки, которые позволят вам изменить значение указателя (а не только того, на что указывает) внутри функция. Об этом объяснил ответ Роберт Кровелла. Также обратите внимание, что это должно быть void, потому что C также не поддерживает перегрузку функции.

Кроме того, при использовании C API в программе на С++ (но вы этого не указали) обычно обертывать такую ​​функцию в шаблоне. Например,

template<typename T>
cudaError_t cudaAlloc(T*& d_p, size_t elements)
{
    return cudaMalloc((void**)&d_p, elements * sizeof(T));
}

Существуют две отличия от того, как вы могли бы вызывать вышеупомянутую функцию cudaAlloc:

  • Передайте указатель устройства напрямую, не используя адрес-оператора (&) при его вызове, и без нажатия на тип void.
  • Второй аргумент elements - это теперь количество элементов, а не количество байтов. Оператор sizeof облегчает это. Это, возможно, более интуитивно понятно, чтобы указывать элементы и не беспокоиться о байтах.

Например:

float *d = nullptr;  // floats, 4 bytes per elements
size_t N = 100;      // 100 elements

cudaError_t err = cudaAlloc(d,N);      // modifies d, input is not bytes

if (err != cudaSuccess)
    std::cerr << "Unable to allocate device memory" << std::endl;

Ответ 3

Я думаю, что подпись функции cudaMalloc может быть лучше объяснена примером. Это в основном назначение буфера через указатель на этот буфер (указатель на указатель), как и следующий метод:

int cudaMalloc(void **memory, size_t size)
{
    int errorCode = 0;

    *memory = new char[size];

    return errorCode;
}

Как вы можете видеть, метод принимает указатель memory на указатель, на котором он сохраняет новую выделенную память. Затем он возвращает код ошибки (в этом случае как целое число, но на самом деле это перечисление).

Функция cudaMalloc может быть сконструирована так же, как и далее:

void * cudaMalloc(size_t size, int * errorCode = nullptr)
{
    if(errorCode)
        errorCode = 0;

    char *memory = new char[size];

    return memory;
}

В этом втором случае код ошибки устанавливается через неявный указатель, равный null (для случая люди вообще не беспокоят код ошибки). Затем возвращается выделенная память.

Первый метод может быть использован как фактический cudaMalloc прямо сейчас:

float *p;
int errorCode;
errorCode = cudaMalloc((void**)&p, sizeof(float));

В то время как второй можно использовать следующим образом:

float *p;
int errorCode;
p = (float *) cudaMalloc(sizeof(float), &errorCode);

Эти два метода функционально эквивалентны, в то время как у них разные подписи, и люди из cuda решили пойти на первый метод, возвращая код ошибки и назначая память через указатель, в то время как большинство людей говорят, что второй метод были лучшим выбором.