Как организованы потоки для выполнения графическим процессором?
Как организованы потоки для выполнения графическим процессором?
Ответы:
Если устройство с графическим процессором имеет, например, 4 многопроцессорных модуля, и они могут запускать 768 потоков каждый: тогда в данный момент в действительности параллельно будет работать не более 4 * 768 потоков (если вы запланировали больше потоков, они будут ожидать их очередь).
темы организованы в блоки. Блок выполняется многопроцессорным устройством. Потоки блока могут быть идентифицированы (проиндексированы) с использованием индексов 1Dimension (x), 2Dimensions (x, y) или 3Dim (x, y, z), но в любом случае x y z <= 768 для нашего примера (применяются другие ограничения) к x, y, z, см. руководство и возможности вашего устройства).
Очевидно, что если вам нужно больше этих 4 * 768 потоков, вам нужно больше 4 блоков. Блоки также могут быть проиндексированы 1D, 2D или 3D. Существует очередь блоков, ожидающих входа в GPU (поскольку в нашем примере GPU имеет 4 мультипроцессора и одновременно выполняется только 4 блока).
Предположим, мы хотим, чтобы один поток обрабатывал один пиксель (i, j).
Мы можем использовать блоки по 64 потока каждый. Тогда нам нужно 512 * 512/64 = 4096 блоков (таким образом, чтобы иметь 512x512 потоков = 4096 * 64)
Распространено организовать (чтобы упростить индексацию изображения) потоки в 2D-блоках, имеющих blockDim = 8 x 8 (64 потока на блок). Я предпочитаю называть это потоками PerBlock.
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 блоков», где блок ожидает назначения одного из мультипроцессоров графического процессора для выполнения 64 потоков.
В ядре пиксель (i, j), обрабатываемый потоком, рассчитывается следующим образом:
uint i = (blockIdx.x * blockDim.x) + threadIdx.x;
uint j = (blockIdx.y * blockDim.y) + threadIdx.y;
Предположим, 9800GT GPU:
https://www.tutorialspoint.com/cuda/cuda_threads.htm
Блок не может иметь больше активных потоков, чем 512, поэтому __syncthreads
может синхронизировать только ограниченное количество потоков. т.е. если вы выполняете следующее с 600 потоками:
func1();
__syncthreads();
func2();
__syncthreads();
тогда ядро должно работать дважды, и порядок выполнения будет:
Примечание:
Суть в том, __syncthreads
что это операция всего блока, и она не синхронизирует все потоки.
Я не уверен в точном количестве потоков, которые __syncthreads
можно синхронизировать, так как вы можете создать блок с более чем 512 потоками и позволить варпу обрабатывать планирование. Насколько я понимаю, точнее сказать: func1 выполняется по крайней мере для первых 512 потоков.
До того, как я отредактировал этот ответ (еще в 2010 году), я измерял 14x8x32 потоков, синхронизированных с помощью __syncthreads
.
Я был бы очень признателен, если бы кто-нибудь еще раз проверил это для получения более точной информации.
__syncthreads
операция всего блока, и тот факт, что он фактически не синхронизирует все потоки, создает неудобства для учащихся CUDA. Поэтому я обновил свой ответ на основе информации, которую вы мне дали. Я очень ценю это.