Распределение памяти CUDA — эффективно ли

#cuda

#cuda

Вопрос:

Это мой код. У меня много потоков, так что эти потоки вызывают эту функцию много раз. Внутри этой функции я создаю массив. Это эффективная реализация?? Если это не так, пожалуйста, предложите мне эффективную реализацию.

 __device__ float calculate minimum(float *arr)
 {
     float vals[9];      //for each call to this function I am creating this arr
                        // Is it efficient?? Or how can I implement this efficiently?
                        // Do I need to deallocate the memory after using this array?
     for(int i=0;i<9;i  )
         vals[i] = //call some function and assign the values
     float min = findMin(vals);
     return min;
 }
  

Комментарии:

1. @mbx: Это CUDA. По умолчанию стека нет, функции компилируются встроенно.

2. Так что в любом случае должно быть эффективным 🙂 Спасибо за ваш совет. Я лучше удалю свой первый комментарий, чтобы не смущать других читателей.

Ответ №1:

В этом коде нет «создания массива». Существует статически объявленный массив. Кроме того, стандартная модель компиляции CUDA будет встроенным расширением __device__ функций, что означает, что vals они будут скомпилированы в локальной памяти или, если возможно, даже в регистрах.

Все это происходит во время компиляции, а не во время выполнения.

Комментарии:

1. @Green Code: Да, очевидно. Это __device__ функция — она компилируется во встроенный код GPU в ядре CUDA, которое ее вызывает. В этой модели компиляции существует четыре типа памяти — глобальная, совместно используемая, постоянная и локальная.

2. Не могли бы вы подробнее рассказать об этих запоминающих устройствах, их спецификациях и их размере (я только что задал тот же вопрос в gamedev.stackexchange)?

3. Все это очень подробно описано в руководстве по программированию на CUDA, если вы хотите его прочитать.

Ответ №2:

Возможно, я чего-то не понимаю, но из опубликованного вами кода следует, что временный массив вам вообще не нужен. Ваш код будет (немного) быстрее, если вы сделаете что-то подобное:

  #include "float.h" // for FLT_MAX

__device__ float calculate minimum(float *arr)
 {
     float minVal = FLT_MAX:
     for(int i=0;i<9;i  )
         thisVal = //call some function and assign the values
         minVal = min(thisVal,minVal);
     return minVal;
 }
  

Там, где действительно требуется массив, нет ничего плохого в объявлении его таким образом (как говорили многие другие).

Ответ №3:

Что касается «float vals[9]», это будет эффективно в CUDA. Для массивов небольшого размера компилятор почти наверняка распределит все элементы по регистрам напрямую. Итак, «vals [0]» будет регистром, «vals[1]» будет регистром и т.д.

Если у компилятора начинают заканчиваться регистры или размер массива превышает примерно 16, то используется локальная память. Вам не нужно беспокоиться о выделении / освобождении локальной памяти, компилятор / драйвер сделают все это за вас.

Устройства с вычислительными возможностями 2.0 и выше имеют стек вызовов, позволяющий выполнять такие вещи, как рекурсия. Например, вы можете установить размер стека равным 6 КБ на поток с помощью:

 cudaStatus = cudaThreadSetLimit(cudaLimitStackSize, 1024*6);
  

Обычно вам не нужно будет самостоятельно прикасаться к стеку. Даже если вы добавите большие статические массивы в функции вашего устройства, компилятор и драйвер увидят, что там есть, и освободят для вас место.

Комментарии:

1. Это не совсем правильно. Если массив динамически индексируется, массив ДОЛЖЕН храниться в памяти устройства (вне чипа), поскольку файл регистра GPU не является динамически адресуемым (индексы регистров должны быть непосредственными значениями в скомпилированной сборке). Итак, если i in vals[i] вычисляется динамически, vals он не будет сохранен в регистрах, независимо от того, насколько он мал. В приведенном выше случае вы могли бы #pragma unroll выполнить цикл, чтобы убедиться, что он i не является динамическим.