Как потоки организованы, чтобы быть выполненными GPU?
Если устройство GPU имеет, например, 4 мультипроцессора, и они могут выполнять 768 потоков каждый: тогда в данный момент времени не более 4*768 потоков будут действительно работать параллельно (если вы запланировали больше потоков, они будут ждать своей очереди).
потоки организованы в блоки. Блок выполняется многопроцессорным устройством. Потоки блока могут быть идентифицированы (проиндексированы) с помощью 1Dimension(x), 2Dimensions (x,y) или 3Dim индексов (x,y,z), но в любом случае xyz <= 768 для нашего примера (другие ограничения применяются к x,y,z, см. руководство и возможности вашего устройства).
Очевидно, что если вам нужно больше, чем эти 4*768 потоков, вам нужно больше, чем 4 блока. Блоки могут быть также проиндексированы 1D, 2D или 3D. Существует очередь блоков, ожидающих поступления в GPU (потому что в нашем примере GPU имеет 4 мультипроцессора и только 4 блока выполняются одновременно).
Предположим, мы хотим, чтобы один поток обрабатывал один пиксель (i,j).
Мы можем использовать блоки по 64 потока в каждом. Тогда нам понадобится 512*512/64 = 4096 блоков (таким образом, чтобы иметь 512x512 потоков = 4096*64)
Обычно (для облегчения индексации изображения) потоки организуются в двумерные блоки, имеющие blockDim = 8 x 8 (64 потока в блоке). Я предпочитаю называть это threadsPerBlock.
dim3 threadsPerBlock(8, 8); // 64 threads
и 2D gridDim = 64 x 64 блоков (4096 необходимых блоков). Я предпочитаю называть это numBlocks.
dim3 numBlocks(imageWidth/threadsPerBlock.x, /* for instance 512/8 = 64*/
imageHeight/threadsPerBlock.y);
Ядро запускается следующим образом:
myKernel <<<numBlocks,threadsPerBlock>>>( /* params for the kernel function */ );
Наконец: будет что-то вроде "очереди из 4096 блоков", где блок ожидает назначения одного из мультипроцессоров GPU для выполнения своих 64 потоков.
В ядре пиксель (i,j), который будет обрабатываться потоком, вычисляется так:
uint i = (blockIdx.x * blockDim.x) + threadIdx.x;
uint j = (blockIdx.y * blockDim.y) + threadIdx.y;
предположим, что графический процессор 9800GT: 14 мультипроцессоров, каждый из которых имеет 8 потоковых процессоров, а размер деформации равен 32, что означает, что каждый потоковый процессор обрабатывает до 32 потоков. 14 * 8 * 32 = 3584 - максимальное количество текущих потоков.
если вы выполняете это ядро с более чем 3584 потоками (скажем, 4000 потоков, и не важно, как вы определяете блок и сетку. Gpu будет обрабатывать их одинаково):
func1();
__syncthreads();
func2();
__syncthreads();
тогда порядок выполнения этих двух функций заключаются в следующем:
1. func1 выполняется для первых 3584 потоков
2.func2 выполняется для первых 3584 потоков
3.func1 выполняется для оставшихся потоков
4.func2 выполняется для оставшихся потоков