Pentium4 производства AMD, часть 2

Отличия

Векторизация

Существенные отличия лежат в глубине вычислительных модулей. Если у NVIDIA вычислительные модули скалярные, то видеокарты AMD имеют гибкие векторные вычислительные модули, которые исполняют сразу несколько операций с несколькими парами аргументов. Не следует путать векторность вычислительных модулей с общей векторностью GPU, все модули которого исполняют одну и ту же инструкцию в один такт. Здесь одна инструкция является комплексной и производит вычисления с несколькими парами аргументов, но в пределах одной нити. Причем, операции с разными парами могут быть различными.

Каждый вычислительный модуль Radeon имеет 4 ALU и одно устройство вычисления функций: тригонометрических, экспонент и корней. То есть, он может, теоретически, выполнить операции с пятью парами аргументов за одну инструкцию.

По гибкости, этот векторный модуль превосходит технологии SSE настольных процессоров. Потому что он принимает на исполнение не одну определённую векторную инструкцию жёсткого формата, например, сложение 4-х пар чисел или умножение 4-х пар чисел, как в стандартном SSE. Инструкция Radeon представляет собой связку (bundle) до 5-ти различных микроинструкций, для каждого устройства — своя. Например, это могут быть два умножения и два сложения. Не все сочетания возможны, но многие, и это гораздо гибче, чем SSE.

модульоперациярегистрывыходной результат
X модуль+ (сложение)R0.x, R1.yR0.x
Y модуль* (умножение)R2.x, R3.yR1.y
Z модуль* (умножение)R4.w, R5.wR0.z
W модуль* (умножение)R6.w, R7.wR1.w
трансцендентный модуль

По таблице видно, что каждый модуль пишет в свою компоненту.

В предыдущем семействе Radeon 4XXX, практически, микроинструкции в связке должны были быть независимыми, поскольку они выполняются одномоментно 4-5 различными устройствами одного вычислительного модуля, принявшего на исполнение одну комплексную инструкцию. Это аналогично практически всем векторным инструкциям SSE. Понятно, что реальный код редко можно разбить на группы независимых инструкций по 4-5 штук. Старые Radeon «на бумаге»  обладали высокой теоретической производительностью, которая вычислялась как количество минипроцессоров (10), умноженное на количество модулей (16) и умноженное на количество вычислительных устройств в модуле (5): 10*16*5=800. И эта вычисленная производительность превосходила таковую у GT200. Реально же, в пиксельных шейдерах, они таких результатов не показывали. И даже в тех немногих неграфических вычислительных программах, которые были созданы для ATI Stream первых версий с поддержкой старых чипов, производительность была, как правило, ниже чем у GeForce. Потому что эти векторные вычислительные модули требовали ручной векторизации всего кода программы. Это равно, как Intel Pentium 4 требовал оптимизации программ под SSE и SSE2, иначе он выглядел бледно. То есть, кроме оптимизации под специфическую архитектуру GPU, что само по себе не просто, надо было ещё дополнительно, в рамках программирования под GPU, оптимизировать для AMD Radeon. Желающих было мало. Особенно в связи с тем, что по многим причинам вообще было мало желающих программировать для GPU AMD.

Но Evergreen производится уже по более тонкому техпроцессу. Соответственно, уменьшается время, необходимое для работы электрической схемы умножения-сложения. Меньше времени занимает и передача результатов, от одного вычислительного блока к другому. Возможно, также и сам дизайн этих вычислительных блоков был улучшен, всё-таки AMD много лет занималась процессоростроением и имеет собственные наработки по модулям умножения и сложения. Которые, наверняка, были более продвинуты, чем у ATI. Так или иначе, но у Evergreen инструкции в связке могут быть отчасти зависимыми. То есть, он может вычислить за такт, одновременно, не только a1+a2, b1+b2, c1+c2, d1+d2, но и a1+a2+a3, b1+b2+b3. Те же 4 сложения во втором случае, но уже операции зависимы даже в пределах одной связки. Возможны и другие комбинации зависимых инструкций. Это упрощает автоматическую векторизацию программы и повышает степень загруженности вычислительных устройств, приближая её к теоретическому максимуму. И саму производительность к теоретически максимальной.

Вот наглядно, что обещает AMD в официальной презентации.

Но компиляторы шейдеров и вычислительных программ, по-видимому, ещё не до конца умеют использовать эти новые возможности. Также, там присутствуют ограничения на чтение и запись операндов. Каждый из четырёх вычислительных модулей (не включая модуль специальных функций) может писать только в свою соответствующую компоненту x, y, z, w векторного регистра результата операции. Да, регистры в Radeon тоже векторные, 4-х компонентные, в отличие от GeForce. Вероятно, есть ещё другие недокументированные ограничения, ибо надлежащего прироста скорости в тестах не наблюдается.

Специальная векторизация, всё же, желательна на данный момент и для Evergreen, для достижения близкой к теоретическому максимуму производительности. А иначе, при значительно более высокой пиковой производительности, новые Radeon не так заметно выигрывают у старых GeForce GTX в реальных тестах, как можно было бы ожидать исходя из спецификаций.

Да, вся эта векторизация относится к вычислениям с числами типа float или целыми числами. Для вычислений с числами типа double используются несколько вычислительных устройств вычислительного модуля, и он может за такт сделать либо одно умножение, либо одно комбинированное умножение-сложение, либо два обыкновенных сложения. Таким образом, скорость вычислений с double в 4-5 раз меньше теоретической скорости float-вычислений, в то время как у Fermi скорость double-расчетов только в два раза меньше. У обоих производителей ещё могут быть некоторые дополнительные накладные расходы на double-вычисления, связанные с большим размером числа double по сравнении c типичным float. Впрочем, обсуждение поддержки double в новых Radeon заслуживает отдельного раздела, так как там много тонкостей.

Эта технология комплексных инструкций называется VLIW. Такая технология используется, например, в процессорах Intel Itanium, и очень даже похожа на то, что реализовано в Radeon. Но, будет неправильно на основе использования составных VLIW-инструкций называть Evergreen суперскалярным процессором. Это несколько безграмотно. Впрочем, это не самая большая безграмотность, связанная с GPU. В конце концов, с определениями не спорят — суперскалярным принято называть тот процессор, который сам находит независимые инструкции в программном коде и отправляет несколько инструкций одновременно на исполнение нескольким устройствам. В данном случае компилятор на этапе компиляции программы пакует несколько инструкций, которые могут исполняться параллельно, в одну большую VLIW-инструкцию.

Надо сказать, это предпочтительный способ, так как блок планировщика (scheduler) современных процессоров очень сложен, и всё равно не обеспечивает оптимальной загрузки исполнительных устройств. Так как он должен работать за всего несколько тактов, в отличие от компиляторов, которые не ограничены во времени. Это одна из главных причин, по которой Itanium имеет самую большую производительность в математических вычислениях с вещественными числами. Это стало возможным благодаря специальной системе команд с поддержкой VLIW, x86 процессоры же ограничены проблемами совместимости и унаследованной системой команд. В конечном итоге, с помощью векторных расширений SSE, x86 процессоры в какой-то мере преодолели изначальные ограничения, но VLIW все равно предоставляет больше возможностей, чем и воспользовались, не ограниченные проблемами совместимости разработчики GPU AMD.

Является ли это большим преимуществом Radeon? В серии 4XXX VLIW был слишком векторный и реальная производительность была много ниже теоретической. Также, архитектура VLIW имеет свои недостатки, связанные со сложностью обработки и перемещения во внутренних регистрах больших инструкций из многих байт. И трудно на основании тестов выделить вклад VLIW в производительность, ведь другие особенности реализации могут негативно сказываться на производительности, и без VLIW, было бы совсем плохо. А для новой серии пока ещё мало ПО. Ниже мы рассмотрим проблемы с программной поддержкой, чтобы можно было сделать определённые выводы. Возможно, с доводкой компилятора, новый VLIW расцветёт, то есть появится новый драйвер, который будет компилировать более оптимальным образом, и производительность возрастёт примерно на 10%.

В целом, смотрится интересно эта эксплуатация контроля над системой команд, с возможностью выбирать удобный формат инструкций. Не зря же для процессора, изначально не обременённого требованиями совместимости, выбрали формат VLIW, и это оправдывает себя.

Ветвления

GPU, мягко говоря, не любят ветвления. Напомним, что все вычислительные модули исполняют одну инструкцию программы нитей за такт и, если различные нити разбегаются (к примеру, одна часть нитей исполняет инструкцию с адресом 100 в программе, а вторая часть нитей исполняет инструкцию с адресом 300), то эта группа нитей будет исполняться в два раза дольше, чем если бы все нити были в одном месте программы. Такие блоки нитей называются warp, в терминологии NVIDIA CUDA. В терминологии AMD, такая группа нитей называется wavefront, что абсолютно одно и тоже. И очень желательно, чтобы в пределах любого варпа или wave-фронта все нити шли одним путем в коде программы, одинаково разрешая все ветвления. Разные варпы, исполняющиеся на одном минипроцессоре (в терминологии CUDA, называется мультипроцессором, а в терминологии AMD, называется SIMD Engine), могут без ущерба разбегаться по программе.

И вот, Radeon питают особую нелюбовь к ветвлениям среди GPU. Она, как пишут в документации к GPU, «dual-issue», т.е. имеет двойную сущность. Или два компонента. Во-первых, размер варпа у Evergreen в два раза больше. Если GeForce исполняет нити пачками по 32 штуки, то есть 32 нити должны быть в одном месте программы и их общую инструкцию подадут на исполнение вычислительным устройствам, то Radeon исполняет нити группами по 64. Один процессор имеет 16 модулей и за 4 такта он исполнит инструкцию 64 нитей, если все они исполняют инструкцию с одинаковым адресом в программе. Если они разбрелись, то потребуется повторить всю процедуру столько раз, сколько получилось различных программных адресов выполняемых инструкций. Допустим, 10 нитей выполняют инструкцию номер 50, еще 30 нитей инструкцию с номером 100 и оставшиеся 24 нити инструкцию с номером 300. Тогда варп будет исполняться 12 тактов вместо четырёх, так как он будет прогнан 3 раза, каждый раз реально будет исполняться одна из трех групп нитей, а другие будут неактивны.

Понятно, что чем меньше размер варпа или wave-фронта, тем лучше. Размер варпа в 64 нити не в два раза хуже размера в 32 нити, но всё же хуже. Для некоторых программ, которые исполняют абсолютно одинаковые вычисления, вообще не будет разницы, а на программах с реальными ветвлениями она будет хорошо заметна.

Но этим обстоятельством нелюбовь Radeon к ветвлениям не ограничивается. Структура машинного кода программы для GPU AMD не совсем обычна. В ней инструкции разных типов помещены в отдельные блоки, а не перемешаны между собой в порядке написания программы. Например, все инструкции управления, ветвления и т.п. размещены в одном блоке, который физически стоит первым в блоке байтов, кодирующих инструкции. Вычислительные инструкции объединены в группы, которые выполняются последовательно и не прерываются инструкциями ветвлений, обращений к памяти и т.п. То есть, например, есть два ветвления в коде и между ними 10 инструкций сложения, две инструкции ветвления будут помещены одна за другой в первый блок инструкций управления, а 10 последовательных сложений образуют цельную группу вычислительных инструкций, которая займет место в блоке вычислительных инструкций.

Арифметические VLIW-инструкции, с несколькими парами операндов, объединены в связки, исполняющиеся последовательно.

Собственно, это прозрачно для программиста, ему не очень обязательно знать внутренний формат представления программы. Но такой формат имеет свои плюсы и минусы, с точки зрения производительности «железа». В частности, переключение с инструкций управления на выполнение вычислительных инструкций само по себе занимает определенное время. Так что лучше, чтобы ветвлений было как можно меньше. GT200 и, видимо, GT300 в этом отношении более лояльны к инструкциям ветвления. Если они когерентны в одном варпе, то, теоретически, там не требуется дополнительного времени на обработку.

Так что GPU AMD снова, на уровне графических процессоров, воспроизводит одно из свойств Pentium 4, а именно — нелюбовь к коду, насыщенному разнообразными ветвлениями. И снова разработчики эксплуатируют отсутствие требований бинарной совместимости кода. Для x86 архитектуры так нельзя сделать, потому что формат размещения машинных инструкций в программе жёстко задан. Такой формат хорош для прямых вычислений по каким-то формулам, для вычислений сложных математических выражений, но без ветвлений. Процессор на линейном участке кода разгонится и будет работать с пиковой производительностью. А производительность более типичного настольного кода, где довольно высокий процент ветвлений, будет страдать. Так что очень желательно при программировании для Radeon использовать оптимизации уменьшения количества ветвлений. Например, развертку циклов и так далее, чтобы максимально увеличить участки кода без ветвлений.

Для уменьшения необходимости в инструкциях ветвлений, используются предикатные регистры и условные инструкции, которые выполняются в зависимости от значений предикатных регистров, установленных предыдущими инструкциями. Это позволяет избавиться от коротких ветвлений, типа if (x < y) a=b*c else a=d*e.

Аналогичная техника есть и в GPU NVIDIA, но в Radeon она реализована немного по-другому, хотя концепция одинакова. Всё это ложится на плечи оптимизирующего компилятора, который должен быть  точно настроенным на архитектуру, чтобы оптимально использовать предикаты. Но и программист также должен иметь это в виду, при выборе алгоритма, каких ветвлений в исходном коде следует бояться, а каких — нет.

 /