Выравнивание памяти cuda

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

struct pt{
int i;
int j;
int k;
}

хотя я не делаю ничего сложного и очевидно, что члены должны иметь назначенные значения, я получаю...

Отвечая на вопрос о позиции 0 стека, в стеке есть только 0 элементов.

Итак, я думаю, что хотя это не массив, возможно, есть проблема с выравниванием памяти в этой точке. Поэтому я изменяю определение в файле заголовка на

struct __align__(16) pt{
int i;
int j;
int k;
}

но затем, когда компилятор пытается скомпилировать файлы хост-кода, которые используют одни и те же определения, появляется следующая ошибка:

error: ожидаемый неквалифицированный идентификатор перед числовой постоянной ошибкой: ожидается ') До числовой постоянной ошибки: ожидаемый конструктор, деструктор, или преобразование типа до '; Маркер

Итак, я должен иметь два разных определения для структур хоста и устройства???

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

Например, как должны быть выровнены следующие два? или, как должна быть выровнена структура с 6 поплавками? или 4 целых числа? опять же, я не использую массивы тех, но все же я определяю множество переменных с этими структурами в ядрах или _ функции _ _.

struct {
    int a;
    int b;
    int c;
    int d;
    float* el;    
} ;

 struct {
    int a;
    int b
    int c
    int d
    float* i;
    float* j;
    float* k;
} ;

Заранее благодарю за любые советы и подсказки

Ответы

Ответ 1

В этом посте много вопросов. Поскольку руководство по программированию CUDA довольно хорошо объясняет выравнивание в CUDA, я просто объясню несколько вещей, которые не очевидны в руководстве.

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

#if defined(__CUDACC__) // NVCC
   #define MY_ALIGN(n) __align__(n)
#elif defined(__GNUC__) // GCC
  #define MY_ALIGN(n) __attribute__((aligned(n)))
#elif defined(_MSC_VER) // MSVC
  #define MY_ALIGN(n) __declspec(align(n))
#else
  #error "Please provide a definition for MY_ALIGN macro for your host compiler!"
#endif

Итак, я должен иметь два разных определения для структур хоста и устройства?

Нет, просто используйте MY_ALIGN(n), как этот

struct MY_ALIGN(16) pt { int i, j, k; }

Например, как должны быть выровнены следующие два?

Во-первых, __align(n)__ (или любой из компонентов компилятора хоста) обеспечивает, чтобы память для структуры начиналась с адреса в памяти, который кратен n байтам. Если размер структуры не кратен n, тогда в массиве этих структур будет добавлено дополнение, чтобы гарантировать, что каждая структура правильно выровнена. Чтобы выбрать правильное значение для n, вы хотите свести к минимуму требуемое заполнение. Как поясняется в руководстве по программированию, аппаратное обеспечение требует, чтобы каждый поток читал слова, выровненные с 1,2,4, 8 или 16 байтами. Так что...

struct MY_ALIGN(16) {
  int a;
  int b;
  int c;
  int d;
  float* el;    
};

В этом случае допустим, что мы выбираем 16-байтовое выравнивание. На 32-битной машине указатель занимает 4 байта, поэтому структура занимает 20 байтов. 16-байтовое выравнивание будет тратить 16 * (ceil(20/16) - 1) = 12 байтов на каждую структуру. На 64-битной машине он будет тратить всего 8 байтов на каждую структуру из-за 8-байтового указателя. Мы можем уменьшить количество отходов с помощью MY_ALIGN(8). Компромисс будет заключаться в том, что аппаратное обеспечение должно будет использовать 3 8-байтовые нагрузки вместо 2 16-байтовых нагрузок для загрузки структуры из памяти. Если вы не испытываете недостатка в нагрузках, это, вероятно, целесообразный компромисс. Обратите внимание, что вы не хотите выровнять меньше 4 байтов для этой структуры.

struct MY_ALIGN(16) {
  int a;
  int b
  int c
  int d
  float* i;
  float* j;
  float* k;
};

В этом случае с выравниванием по 16 байт вы тратите только 4 байта на каждую структуру на 32-битные машины или 8 на 64-разрядные машины. Это потребует двух 16-байтовых нагрузок (или 3 на 64-битной машине). Если мы выровняемся с 8 байтами, мы можем полностью исключить отходы с 4-байтовым выравниванием (8-байтовые на 64-битных машинах), но это приведет к чрезмерным нагрузкам. Опять же, компромиссы.

или, как должна быть выровнена структура с 6 поплавками?

Опять же, компромиссы: либо отбросить 8 байт на одну структуру, либо потребовать две нагрузки для каждой структуры.

или 4 целых числа?

Здесь нет компромиссов. MY_ALIGN(16).

снова, я не использую массивы тех, но все же я определяю множество переменных с этими структурами в ядрах или _ функции устройства _.

Хммм, если вы не используете массивы из них, вам может вообще не понадобиться выровнять. Но как вы их назначаете? Как вы, вероятно, видите, все эти отходы важны, чтобы беспокоиться о них, это еще одна веская причина для поддержки структур массивов над массивами структур.

Ответ 2

В наши дни вы должны использовать С++ 11 alignas спецификатор, который поддерживается g++ (включая совместимые версии с текущим CUDA) и IIANM на nvcc. Это должно избавить вас от необходимости прибегать к макросам.