Оптимизация копирования по потокам и заполнение нулями данных в регистрах / L1

Я пишу ядро, в котором, помимо прочего, каждый поток заполняет одну переменную данными, составляющими младшие байты, и дополняет остальные (при условии, что они имеют порядок байтов). Это выполняется неоднократно и неравномерно по потокам, поскольку некоторые потоки могут иметь больше байтов для копирования в свои переменные и меньше заполнителей, а некоторые потоки — меньше и больше заполнителей. И результат, и незаполненные данные находятся либо в регистре (для меньших размеров), в разделяемой памяти или в локальной памяти (которая должна быть покрыта L1).

Другими словами, предположим, что каждый поток выполняет:

T padded;
pad_high_bytes(padded, my_low_bytes, my_num_low_bytes);
do_stuff(padded);

где мы имеем:

template <typename T>
__device__ __forceinline__
void pad_high_bytes(
T&                                result,
const unsigned char* __restrict__ low_bytes,
unsigned                          num_low_bytes);

Если T большой (скажем, struct { int data[50]; }) тогда я думаю, что я должен просто использовать код устройства CUDA memcpy(), Однако обычно это не так — размер T обычно равен 4 или 8, а число младших байтов обычно составляет от 1 до 3, и даже 0 не редкость.

Я, очевидно, могу перебрать байты и надеяться на лучшее. Я также могу «перебрать все целые», если осталось скопировать более 4 байтов, а затем перебрать оставшиеся байты. Но:

  1. Могу ли я сделать лучше?
  2. Должен ли я как-то учитывать выравнивание? Или это слишком много хлопот?
  3. Должны ли копирование и заполнение полностью разделяться в коде или я должен как-то их объединить?
  4. Что было бы разумным значением для sizeof (T), при котором нужно переключиться на memcpy () — если вообще?

Помните, что функция основана на типе результата, поэтому, если вам есть, что сказать в отношении меньших / больших Т, это полезно.

-1

Решение

Я действительно изо всех сил пытаюсь понять, как вопрос Q1-3, который можно перефразировать как «мои слова — оптимальный дизайн для этой неопределенно описанной задачи», действительно отвечает. Так что я не собираюсь даже пытаться.

Q4 является подотчетны:

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

Если вы не знаете ни одну из этих двух вещей, то memcpy по-прежнему лучший выбор, просто потому, что он упрощает код и открывает возможность идиоматической оптимизации за кулисами в рамках цепочки инструментов. Единственный раз, когда я бы посоветовал это сделать, если у вас есть возможность объединить другие операции с копией, и в этом случае выполнение чего-либо самостоятельно, вероятно, все еще имеет смысл.

2

Другие решения

Других решений пока нет …