Программирование GPGPU позволяет только выполнение SIMD-инструкций? - PullRequest
2 голосов
/ 07 октября 2011

Программирование GPGPU позволяет только выполнение команд SIMD? Если это так, то это должно быть утомительной задачей переписать алгоритм, который имеет был разработан для работы на общем процессоре, чтобы работать на графическом процессоре? Также есть шаблон в алгоритмах, которые можно преобразовать в архитектуру SIMD?

1 Ответ

7 голосов
/ 26 апреля 2012

Ну, не совсем точно, что GPGPU поддерживает только SIMD-исполнение. Многие графические процессоры имеют некоторые не SIMD компоненты. Но в целом, чтобы использовать все преимущества графического процессора, вам нужно использовать код SIMD.

Однако, вы НЕ обязательно пишите SIMD инструкции. То есть GPU SIMD не то же самое, что CPU SIMD - то есть не то же самое, что написание кода для использования преимуществ x86 SSE (Stream SIMD Extensions) и т. Д. Действительно, как один из тех, кто использует CPU SIMD для вас (Я был сильно вовлечен в Intel MMX, один из первых, и следил за развитием FP SIMD). Я часто чувствую себя обязанным исправлять людей, которые говорят, что процессоры, подобные Intel, имеют инструкции SIMD. Я предпочитаю считать их упакованными векторными инструкциями, хотя я неохотно называю их SIMD-упакованными наборами векторных инструкций только потому, что все неправильно используют имя. Я также подчеркиваю, что наборы управления процессором SIMD, такие как MMX и SSE, могут иметь упакованные векторные SIMD-модули исполнения - целочисленные и с плавающей запятой и т. Д. - но они не имеют потока управления SIMD и обычно не имеют доступа к памяти SIMD. (он же разброс / сбор (хотя Intel Larrabee двигался в этом направлении)).

Некоторые страницы на моей вики-странице comp-arch.net об этом (я пишу об архитектуре компьютера для своего хобби): - http://semipublic.comp -arch.net / wiki / SIMD - http://semipublic.comp -arch.net / wiki / SIMD_packed_vector - http://semipublic.comp -arch.net / wiki / Difference_between_vector_and_packed_vector - http://semipublic.comp -arch.net / wiki / Single_Instruction_Multiple_Threads_ (SIMT) хотя я извиняюсь за то, что еще не написал страницу, в которой говорится о упакованных в SIMD векторных инструкторах, как в Intel MMX или SIMD.

Но я не ожидаю, что вы прочитаете все вышеперечисленное. Позвольте мне попытаться объяснить.

Представьте себе, что у вас есть фрагмент кода, который выглядит примерно так, когда он написан простым скалярным способом:

// operating on an array with one million 32b floating point elements A[1000000]
for i from 0 upto 999999 do
     if some_condition(A[i]) then
           A[i] = function1(A[i])
     else
           A[i] = function2(A[i])

где function1 () и function2 () достаточно просты для встраивания - скажем, function1 (x) = x * x и function2 (x) = sqrt (x).

На процессоре. чтобы использовать что-то вроде SSE, вы должны были бы (1) разделить массив на куски, скажем, размером 256-битного AVX, (2) обработать оператор IF самостоятельно, используя маски или тому подобное. Что-то вроде:

for i from 0 upto 999999 by 8 do
     register tmp256b_1 = load256b(&A[i])
     register tmp256b_2 = tmp256b_1 * tmp256b_1
     register tmp256b_3 = _mm_sqrt_ps(tmp256b_1) // this is an "intrinsic"
                                                 // a function, possibly inlined
                                                 // doing a Newton Raphson to evaluate sqrt.
     register mask256b = ... code that arranges for you to have 32 1s in the "lane" 
                         where some_condition is true, and 0s elsewhere...
     register tmp256b_4 = (tmp256b_1 & mask) | (tmp256b_3 | ~mask);
     store256b(&A[i],tmp256b_4)

Возможно, вы не думаете, что это так плохо, но помните, это простой пример. Представьте себе несколько вложенных IF, и так далее. Или представьте, что «some_condition» является клочковатым, так что вы можете сэкономить много ненужных вычислений, пропустив разделы, где все это function1 или все function2 ...

for i from 0 upto 999999 by 8 do
     register mask256b = ... code that arranges for you to have 32 1s in the "lane" 
                         where some_condition is true, and 0s elsewhere...
     register tmp256b_1 = load256b(A[i])
     if mask256b == ~0 then
         register tmp256b_2 = tmp256b_1 * tmp256b_1
         store256b(&A[i],tmp256b_2)
     else mask256b == 0 then
         register tmp256b_3 = _mm_sqrt_ps(tmp256b_1) // this is an "intrinsic"
         store256b(&A[i],tmp256b_3)
     else
         register tmp256b_1 = load256b(&A[i])
         register tmp256b_2 = tmp256b_1 * tmp256b_1
         register tmp256b_3 = _mm_sqrt_ps(tmp256b_1)
         register tmp256b_4 = (tmp256b_1 & mask) | (tmp256b_3 | ~mask);
         store256b(&A[i],tmp256b_4)

Я думаю, вы можете получить картину? И это становится еще сложнее, когда у вас есть несколько массивов, и иногда данные выровнены по 256-битной границе, а иногда нет (как обычно, например, в вычислениях трафарета, где вы работаете со всеми выравниваниями).

Теперь, примерно то, как это выглядит на чем-то вроде графического процессора:

// operating on an array with one million 32b floating point elements A[1000000]
for all i from 0 upto 999999 do
     if some_condition(A) then
           A = function1(A)
     else
           A = function2(A)

Разве это не похоже на оригинальный скалярный код? Единственная реальная разница в том, что вы потеряли индексы массива, A [i]. (На самом деле, некоторые языки GPGPU содержат индексы массивов, но большинство, о которых я знаю, этого не делают.)

Теперь я пропустил (a) C-подобный синтаксис Open / CL, (b) все настройки, необходимые для подключения кода Open / CL к вашему коду C или C ++ (есть гораздо лучшие языки, чем CUDA или OpenCL - у них много ошибок, но они доступны во многих местах, как на CPU, так и на GPU [**]). Но я думаю, что представил суть вопроса:

Ключевым моментом в вычислениях GPGPU является то, что вы пишете SIMD, параллельные данные. Но вы пишете это на более высоком уровне, чем вы пишете код SSE в стиле CPU. Более высокий уровень, чем встроенные функции компилятора.

Во-первых, компилятор GPGPU, например, компилятор OpenCL или CUDA, обрабатывает много управления данными за вашей спиной. Компилятор организует поток управления, операторы IF и т. Д.

Кстати, обратите внимание, как я отмечал [**], иногда так называемый компилятор SIMD GPGPU может генерировать код, который будет работать как на процессорах, так и на графических процессорах.Т.е. компилятор SIMD может генерировать код, который использует наборы инструкций SIMD процессора.

Но сами графические процессоры имеют специальную аппаратную поддержку, которая выполняет этот код SIMD, скомпилированный соответствующим образом, гораздо быстрее, чем он может работать на процессоре с использованием инструкций SIMD процессора.Самое главное, что у графических процессоров гораздо больше исполнительных блоков - например, такой процессор, как AMD Bulldoser, имеет 2 набора FMACS шириной 128 бит, то есть способен выполнять 8 FMAC за такт.Умножьте количество процессоров на чипе, скажем, на 8, что даст вам, возможно, 64 за такт.Принимая во внимание, что современный GPU может иметь 2048 32-битных FMAC в каждом цикле.Даже если он работает на частоте 1/2 или 1/4 тактовой частоты, это большая разница.

Как графические процессоры могут иметь гораздо больше аппаратного обеспечения?Ну, во-первых, они обычно больше чипов, чем процессор.Но, кроме того, они, как правило, не тратят (некоторые говорят, что они «тратят») оборудование на такие вещи, как большие кеши и неупорядоченное выполнение, на которое его тратят процессоры.Процессоры пытаются ускорить одно или несколько вычислений, в то время как графические процессоры выполняют много вычислений параллельно, но индивидуально медленнее, чем процессор.Тем не менее, общее количество вычислений, которые GPU может выполнять в секунду, намного выше, чем CPU.

FGPU имеют другие аппаратные оптимизации.Например, они запускают намного больше потоков, чем процессор.В то время как процессор Intel имеет 2 гиперпотока на процессор, что дает вам 16 потоков на 8-ядерном чипе, у GPU может быть сотни.И так далее.

Наиболее интересным для меня, как компьютерного архитектора, является то, что многие графические процессоры имеют специальную аппаратную поддержку потока управления SIMD.Они делают манипулирование этими масками намного эффективнее, чем на процессоре SSE.

И т. Д.


В любом случае, я надеюсь, что я высказал свою точку зрения

  • В то время как вам нужно написать код SIMD для работы в системе GPGPU (например, OpenCL).

  • Не следует путать этот видSIMD с кодом SIMD, который вы должны написать, чтобы использовать преимущества Intel SSE.

Это намного чище.

Все больше и больше компиляторов позволяют запускать один и тот же кодкак DCPU, так и GPU.Т.е. они все больше поддерживают чистый стиль кодирования "настоящая SIMD", а не фальшивый стиль кодирования "псевдо-SIMD", который был необходим для использования преимуществ MMX, SSE и AVX до настоящего времени.Это хорошо - такой код одинаково «хорош» для программирования как на CPU, так и на GPU.Но GPU часто запускает его намного быстрее.Есть статья Intel под названием «Разоблачение мифа о 100X GPU и CPU: оценка производительности вычислений на CPU и GPU», http://www.hwsw.hu/kepek/hirek/2010/06/p451-lee.pdf. В нем говорится, что GPU в среднем «только» в 2,5 раза быстрее.Но это после большой агрессивной оптимизации.Код GPU часто легче написать.И я не знаю, как вы, но я думаю, что «только» в 2,5 раза быстрее - это не так уж и сложно.Тем более что код GPGPU часто проще для чтения.

Теперь бесплатного обеда нет.Если ваш код, естественно, параллелен данным, то отличноНо некоторого числа нет.Это может быть неприятно.

И, как и на всех машинах, у графических процессоров есть свои причуды.

Но если ваш код, естественно, параллелен к данным, вы можете получить отличное ускорение, с кодом, который намного больше

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

...