Сумма префикса с использованием CUDA
У меня проблемы с пониманием кода CUDA для наивной суммы префикса.
Это код из https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch39.html В примере 39-1 (простое сканирование) у нас есть такой код:
__global__ void scan(float *g_odata, float *g_idata, int n)
{
extern __shared__ float temp[]; // allocated on invocation
int thid = threadIdx.x;
int pout = 0, pin = 1;
// Load input into shared memory.
// This is exclusive scan, so shift right by one
// and set first element to 0
temp[pout*n + thid] = (thid > 0) ? g_idata[thid-1] : 0;
__syncthreads();
for (int offset = 1; offset < n; offset *= 2)
{
pout = 1 - pout; // swap double buffer indices
pin = 1 - pout;
if (thid >= offset)
temp[pout*n+thid] += temp[pin*n+thid - offset];
else
temp[pout*n+thid] = temp[pin*n+thid];
__syncthreads();
}
g_odata[thid] = temp[pout*n+thid1]; // write output
}
Мои вопросы
- Зачем нам нужно создавать временную общую память?
- Зачем нам нужны переменные "pout" и "pin"? Что они делают? Поскольку здесь мы используем только один блок и максимум 1024 потока, можем ли мы использовать только threadId.x для указания элемента в блоке?
- В CUDA, мы используем один поток, чтобы сделать одну операцию добавления? Это похоже на то, что один поток делает то, что можно сделать за одну итерацию, если я использую цикл for (зацикливание потоков или процессоров в OpenMP с учетом одного потока на один элемент в массиве)?
- Мои предыдущие два вопроса могут показаться наивными... Я думаю, что ключ в том, что я не понимаю отношения между вышеупомянутой реализацией и псевдокодом следующим образом:
for d = 1 to log2 n do
for all k in parallel do
if k >= 2^d then
x[k] = x[k – 2^(d-1)] + x[k]
Я впервые использую CUDA, поэтому буду признателен, если кто-нибудь ответит на мои вопросы...
1 ответ
1- быстрее помещать материал в общую память (SM) и выполнять там вычисления, а не использовать глобальную память. После загрузки SM важно синхронизировать потоки, поэтому __syncthreads.
2- Эти переменные, вероятно, существуют для пояснения изменения порядка в алгоритме. Это просто для переключения определенных частей:
temp[pout*n+thid] += temp[pin*n+thid - offset];
Первая итерация; pout = 1 и pin = 0. Вторая итерация; pout = 0 и pin = 1. Он смещает выход N для количества на нечетных итерациях и смещает вход на четных итерациях. Возвращаясь к вашему вопросу, вы не можете достичь того же с помощью threadId.x, потому что он не изменится в цикле.
3 и 4 - CUDA выполняет потоки для запуска ядра. Это означает, что каждый поток запускает этот код отдельно. Если вы посмотрите на псевдокод и сравните его с кодом CUDA, вы уже распараллелили внешний цикл с CUDA. Таким образом, каждый поток будет выполнять цикл в ядре до конца цикла и будет ждать завершения каждого потока перед записью в глобальную память.
Надеюсь, поможет.