Параллельные вычислительные процессоры NVIDIA: настоящее и будущее

Введение

Когда пишут об архитектуре NVIDIA CUDA, принято начать с экскурса в историю развития GPU, роста их функциональных возможностей, как они, шаг за шагом, превращались в универсальные вычислительные устройства. Но, в данной статье этому не будет уделено никакого внимания, видеоускорители уже эволюционировали в процессоры. И разработчикам, и пользователям программного обеспечения в принципе все равно, был ли этот путь ровным или извилистым. Как правило, новые процессорные архитектуры — есть эволюционное развитие предшествующих. Например, не будет очень большим преувеличением сказать, что процессор Core есть сильно улучшенный, доведенный до архитектурного совершенства, многоядерный Pentium II–III. А процессоры Phenom отличаются от первых Атлонов поддержкой SSE, прикрученным контроллером памяти, большими кэшами, 64-битным расширением набора инструкций и мелкими архитектурными улучшениями, такими как предвыборка данных и новые алгоритмы предсказания условных переходов. Но это нельзя сказать о технологии CUDA. Это принципиально новая архитектура, которая стала возможной только благодаря невероятному улучшению технологических процессов за последние годы. Миллиарды транзисторов на кристалл обернулись сотнями вычислительных CUDA-модулей. Причем, важно, что этот рост техпроцесса произошел «вширь», а не «вглубь». То есть, вылился не в повышение тактовой частоты, а именно в увеличение площадей кристалла. Если бы открыли какой-то способ повышения тактовой частоты, вместо утончения норм производства, то ни о какой CUDA и речи бы ни шло. Так как современные, высоко конвейеризированные CPU достигли бы высоких частот и превосходной производительности в однопоточном режиме, а места для большого количества низкочастотных по своей природе CUDA-процессоров, на кристалле не нашлось бы.

Но случилось так, как случилось и технологии многоядерной мультипоточности, среди которых CUDA — самая радикальная, выходят на первый план.

Итак, суть архитектуры — это размещение на кристалле нескольких десятков процессорных ядер с собственной памятью, каждое из которых одновременно выполняет несколько сотен программных потоков. И, в данном случае термин «нить», как нельзя лучше подходит для описания одной из тысяч параллельно выполняющихся частей CUDA-программы. Как будет описано далее, эти нити переплетаются между собой и они ещё сплетены в более крупные структуры, как отдельные тонкие провода в большом кабеле. Процессорные ядра, называемые в CUDA-терминологии мультипроцессорами, имеют собственный доступ к глобальной памяти, расположенной на видеоплате и устройство обменивается данными с CPU через шину PCI-Express.

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

Необходимо сразу отметить, что программироваться это CUDA-устройство должно по принципам настоящего суперкомпьютера. С мерками программирования для PC, к нему подходить нельзя.

Фундаментальные основы

Есть два факта, их можно назвать законами вычислительной техники, которые составляют теоретическую основу востребованности высокопараллельных вычислительных архитектур. Потребляемая мощность процессора пропорциональна примерно квадрату тактовой частоты, примерно степени 2,5. То есть, процессор с тактовой частотой 3 ГГц потребляет больше, чем 9 процессоров частотой 1 ГГц. Таким образом, для параллельной программы энергоэффективность массы мелких процессоров будет выше в три раза. Иными словами, многоядерный процессор, с той же потребляемой энергией, будет в три раза производительнее при исполнении параллельного кода.

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

А если программа имеет десять потоков, то, скорее всего, можно будет использовать и двадцать, и пятьдесят нитей. И дальше — больше: где сто, там и тысяча. И так далее. Таким образом, если отправляться в мир параллельных вычислений, то имеет смысл бросаться, как в омут головой. Потому, что после превышения количества потоков определенной величины уже становится безразлично, сколько их, их просто много.

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

Важно также, что конструировать низкочастотный процессор гораздо легче. Проще реализовать разнообразную сложную логику, большинство инструкций исполняются за один такт, что упрощает планировщик выполнения инструкций и требует меньшего количества разнообразных буферов и очередей инструкций. А также упрощает и делает более удобной саму ISA. (ISA — Instruction Set Architecture, архитектура системы команд). И пропускная способность памяти растет гораздо быстрее, чем уменьшается её латентность. Таким образом, обеспечить данными множество низкочастотных CPU гораздо проще и дешевле, чем один высокочастотный. Так как он будет часто простаивать, в ожидании данных из медленной памяти.

Эти факты, взятые вместе, делают многопоточную технологию CUDA востребованной и перспективной.

Современная архитектура

Среди всевозможных компьютерных задач можно выделить три больших класса: серверные задачи работы с базами данных, задачи управления (например, внутренняя логика какой–нибудь компьютерной игры или симулятора) и вычислительные задачи. Архитектура CUDA, в её текущей аппаратной реализации ориентирована, в первую очередь, на вычислительные задачи. Так называемые compute bound applications. Это не обязательно решение математических уравнений, это может быть и обработка изображений, и проверка ключей, и анализ строк. Но суть в том, что надо считать, считать и считать, а не различные функции вызывать с ветвлениями.

Итак, возьмем для конкретности последнюю реализацию CUDA в графическом процессоре GT200. Он имеет 30 мультипроцессоров, один мультипроцессор работает на частоте примерно 1,4 GHz, имеет 8 исполнительных устройств общего назначения, выполняющих за такт типичные операции, как то: сложение и умножение вещественных чисел типа float, сравнения, условные переходы, логические операции, операции с регистрами.

Нити

Один мультипроцессор может одновременно выполнять 1024 программных нитей. Но исполнительных устройств всего 8, не считая специального устройства вычисления библиотечных функций типа синуса и косинуса. Поэтому инструкции всех 1024 потоков в один момент исполняться не могут. Нити разбиты на группы по 32 штуки, называемые warp. Этот термин можно художественно перевести как пучок нитей, само английское значение слова имеет, видимо, морское происхождение, связанное с тросами. Очень, кстати, показательно. В один момент исполняется один варп, он исполняется 4 такта (8 исполнительных устройств за 4 такта исполняют 32 операции). Если там нет длинной специальной инструкции. Но, из всех 32 нитей, в один заход реально исполняются только те, которые находятся в одном месте программы. То есть, исполняют одну и ту же инструкцию программы. Техническими словами, если совпадают указатели инструкций, другими словами — адреса инструкций в коде программы.

Каждая нить CUDA-приложения исполняет одну и ту же программу, но алгоритму доступен номер нити среди всех запущенных, и поэтому алгоритм может произвольно меняться, в зависимости от её номера. Можно запустить хоть 10000 различных подпрограмм, для каждой нити выбрав свою, в самом начале кода. Но это будет неэффективно, из-за вышеприведенной особенности исполнения варпа целиком. Когда все нити, внутри каждого варпа, имеют одинаковый путь исполнения, достигается максимальная производительность.

Если ветвления, в программе нити, уводят её в сторону от других, она начинает исполнять отдельный кусок программы, тогда время исполнения мультипроцессором целого варпа удваивается. Потому, что сначала он исполняет инструкцию, соответствующую 31 нити, а потом инструкцию оставшейся нити. Если половина варпа, 16 нитей, будет в одном месте программы, а другая половина — в другом, то варп тоже будет исполняться в два раза медленней. А если все нити варпа разбредутся по разным частям программы и будут выполнять каждая свои инструкции, то скорость упадет в 32 раза.

Но, именно для вычислительных задач, такой сильный разброс не характерен и с ним можно успешно бороться, что определяет наилучшую область применения технологии. В плотных вычислительных циклах нити имеют, как правило, сходный путь. Впрочем, даже при полностью сериализованном (от английского слова serial), иными словами, последовательном исполнении, CUDA-устройство имеет неплохую производительность, благодаря большому количеству мультипроцессоров и достаточно высокой тактовой частоте. И генератор машинного кода для CUDA анализирует программу, детектирует циклы и вставляет точки реконвергенции для нитей, где они ждут друг друга, если расходились, чтобы дальше пойти дружным шагом, а не совсем разбредаться из-за первого же дивергентного ветвления.

И, надо сказать, что уже различные варпы, то есть пучки или связки нитей по 32 штуки, могут выполняться полностью независимо. Каждый варп может выполнять инструкции из любого места программы. Имея 1024 нити, в стадии выполнения на мультипроцессоре, можно, таким образом, иметь 32 активных варпа, исполняющихся параллельно. Для исполнения выбирается тот варп, для инструкций которого подгружены данные из глобальной памяти, т.е. который готов для исполнения.

Схематическое изображение исполняющейся CUDA-программы «в разрезе». Большие синие прямоугольники обозначают 30 мультипроцессоров. На каждом 4 активных блока нитей, обозначенных кружками. Каждый блок состоит из 4 варпов по 32 нити. Варп обозначен прямоугольником. Варп состоит из двух полуварпов по 16 нитей. Каждая нить обозначена точкой. Итого имеем 15360 (30*4*4*32) одновременно исполняющихся нитей.

 /