Журнал «Компьютерра» № 13 от 04 апреля 2006 года - Компьютерра. Страница 11
Как только число инструкций в программе достигло десятков, пользоваться ассемблером стало не так удобно. Поэтому к тому же времени формируются специализированные C-подобные языки высокого уровня. Заслуживают упоминания как минимум три из них: cg от nVidia, HLSL из DirectX и GLSL из OpenGL. Все они очень похожи, но, к сожалению, отличаются лексическими и синтаксическими деталями. Их компиляторы к сегодняшнему дню стали довольно зрелыми, способны оптимизировать код, хотя небольшая вероятность наткнуться на неправильно скомпилированный шейдер еще остается. Отказываться от высокоуровневых языков сейчас приходится лишь в исключительных случаях, например, чтобы уложиться в лимит регистров или инструкций.
Достоинства графических чипов: высокая производительность, точность и достаточная простота программирования не могли не быть замеченными, в первую очередь университетами. В 2003 году наблюдается всплеск научных статей, посвященных алгоритмам и принципам расчетов общего назначения средствами графических плат (GPGPU[GPGPU – General-Purpose Computation on GPUs]). С 2004 года специально организуются научные конференции.
В этой связи любопытно поведение двух крупнейших игроков на рынке графических чипов. Если aTI, по-видимому, заняла выжидательную, консервативную позицию: «GPU нужен только для игр», то nVidia, наоборот, проявляет заметную активность. Она пропагандирует идею GPGPU, организует курсы, призывая исследователей пользоваться ее аппаратурой для неграфических вычислений[gpgpu.org/s2005], предоставляет им временную или постоянную работу. Под эгидой nVidia издано уже два бестселлера «GPU Gems»[ developer.nvidia.com/object, developer.nvidia.com/object/gpu_gems_2_home.html], в которые вошли главы по вычислениям общего назначения. С 2002 года сотрудники фирмы ведут сайт gpgpu.org, пытающийся систематизировать все результаты в этой области. nVidia продает ряд продуктов для нужд киноиндустрии, на деле доказывая нешуточность идеи.
Каковы результаты этой активности? Судя по публикациям, GPU удается найти применение в самых различных областях высокопроизводительных вычислений, включая высококачественный рендеринг, трассировку лучей, обработку изображений и сигналов, машинное зрение, компрессию, поиск и сортировку, биоинформатику, решение систем линейных уравнений, моделирование физических эффектов. Достигаемое ускорение колеблется от случая к случаю, но типично составляет несколько крат по сравнению с расчетом на центральном процессоре. Вы спросите, отчего же CPU так катастрофически проигрывают, если они изготовляются на таких же, если не на лучших полупроводниковых фабриках, содержат сопоставимое число транзисторов[Буквально одно сравнение high-end-продуктов в подтверждение: 376 млн. транзисторов в двухъядерном Intel Pentium EE 955 против 384 млн. в ATI Radeon X1900XTX], а их рабочие частоты в разы выше, чем у GPU?
Одно арифметическое устройство, оперирующее числами с плавающей точкой, при современном технологическом процессе производства чипов занимает на кристалле меньше одного квадратного миллиметра[Эти и последующие числа раздела взяты из книги «GPU Gems 2»]. Поэтому во всем чипе их можно иметь сотни, но проблема не в количестве устройств, а в том, как их все загрузить работой. К сожалению, на этом пути есть препятствия.
В первую очередь – память. По закону Мура, каждый год количество транзисторов на чипе возрастает наполовину, возрастает (но медленнее) и скорость их работы, так что суммарно можно говорить примерно о семидесятипроцентном повышении теоретической производительности устройств. Почему теоретической? Да потому, что пропускная способность памяти ежегодно возрастает примерно на 25%, а ее латентность (задержка обращения к новому участку памяти) сокращается и того медленнее – всего на 5% в год. Поэтому если не предпринимать дополнительных усилий, то самое слабое звено и будет определять производительность всей системы.
Центральный процессор обеспечивает просто райские условия для разработчика: любая инструкция в программе может считать или записать произвольную ячейку большой оперативной памяти. На деле это выливается в совершенно нерегулярный набор обращений к памяти. И чтобы ее латентность не была столь критической, в процессор приходится встраивать быструю кэш-память. И встраивать много – кэш сейчас занимает не меньше половины площади кристалла, а значит, ее не занимают вычислительные блоки. Причем во многих сценариях большой кэш оказывается неэффективен, к примеру, если обращение к ячейке памяти происходит лишь единожды, как при обработке потоков.
Второй важной причиной является последовательный характер обычных программ – наборов инструкций, которые для получения желаемого результата должны выполняться друг за другом. Если одна инструкция задержится медленной памятью, то задержится исполнение и всех остальных. Конечно, не все инструкции являются зависимыми и поэтому могут выполняться одновременно. Но явно эта независимость в программе не отражена, так что на выявление скрытого параллелизма тратится другая заметная порция площади кристалла. В самом лучшем случае удается наскрести работу для считанных единиц исполнительных устройств.
Как же эти проблемы решаются в GPU? При описании графического конвейера неоднократно подчеркивалось, что внутри каждого блока конвейера выполняются независимые действия: вершины обрабатываются независимо одна от другой, аналогичное утверждение справедливо для треугольников и т. д. Поэтому не только отдельные этапы конвейера функционируют одновременно, но и на каждом этапе идет параллельная обработка. В этом смысле внутри GPU выделяются наборы вершинных и пиксельных процессоров (рис. 2). Для обеспечения произвольного порядка обработки фрагментов текстура, в которую выполняется рисование, не может в то же самое время использоваться и для выборки, то есть видеопамять делится на непересекающиеся участки только-для-чтения и только-для-записи. Также не могут совпадать обновляемые точки в целевой структуре, поскольку итоговое положение каждого фрагмента фиксируется еще на этапе растеризации. Этими ограничениями достигается достаточное свойство параллельности пиксельных процессоров. Теперь, несмотря на то что каждый шейдер – это последовательная программа, при задержке обращения к памяти при обработке одного фрагмента GPU может не простаивать, а переключиться на другой фрагмент – кандидатов всегда достаточно. Имеются и элементы явного параллелизма в шейдерах: каждая ассемблерная инструкция может выполнять операцию не со скалярами, а сразу с четырехэлементными векторами[Число четыре возникло не случайно – именно такова размерность гомогенного пространства, и таково число компонентов в полупрозрачной цветной текстуре. Векторными операциями можно не пользоваться, но тогда эффективность GPU резко снижается], есть комбинированная инструкция умножь-затем-прибавь.
Без ячеек памяти, которые можно и считывать, и записывать, совсем обойтись, конечно, нельзя. Каждому шейдеру для этой цели предоставляются регистры, их мало (табл. 1), приходится экономить, но благодаря этому все промежуточные вычисления ведутся без обращения к внешней памяти, куда попадает лишь финальный результат. Малый размер шейдера и его общность для всех фрагментов преследуют ту же цель – хранить код программы не в памяти, а внутри процессора. Ведется последовательная политика, включающая разъяснительные мероприятия среди разработчиков, увеличения количества арифметических операций между последовательными обращениями к памяти.
Во всем конвейере главными претендентами для выполнения неграфических расчетов, безусловно, являются вершинные и пиксельные процессоры. Рассмотрим для примера плату GeForce 6800 Ultra. В ней имеется шесть вершинных процессоров, каждый из которых способен за такт выполнять максимум две арифметические операции над четырехэлементными векторами, а также шестнадцать пиксельных процессоров, способных на три векторные операции за такт. Умножая на частоту чипа 425 МГц, получаем верхнюю оценку производительности в 100 Гфлопс. Проделав те же выкладки для новейшей GeForce 7900 GTX, имеющей уже восемь вершинных и двадцать четыре пиксельных процессора и функционирующей на частоте 650 МГц, получаем почти 230 Гфлопс.