Технология вычислений общего назначения на графическом ускорителе. Лыков Кирилл Олегович Содержание 1.Введение. 1.1 Преимущества GPGPU. 1.2 Программируемость и гибкость 1.3 Сложности и Ограничения 2.Архитектура графического ускорителя 2.1 Конвейер GPU. 2.2 Введение в модель программирования на GPU. 2.3 Модель памяти GPU. 2.4 Контроль потоков на GPU. 3. Система программирования для GPU. 3.1 Языки программирования шейдеров высокого уровня. 3.2 Средства отладки. 3.3 Профилировщики. 4. Технология GPGPU. 5. Применение. 6.Заключение. 7.Литература 1. Ведение Развитие современных графических ускорителей или graphic processor unit (в дальнейшем GPU) привело к появлению графических ускорителей с программируемым конвейером. Если раньше программист графических приложений мог использовать только фиксированную функциональность GPU, то теперь 2 стадии обработки информации в конвейере GPU стали программируемыми. Более точно программируемой стала обработка вершин, что позволило создавать, в частности, более разнообразные модели освещения, а так же обработка фрагментов. Столь коренное изменение архитектуры GPU привело к возможности использования GPU не только для целей компьютерной графики, но и для задач, которые раннее решались на CPU, таким образом, появилась Технология Вычислений Общего Назначения на Графическом Ускорителе или general purpose computation on graphic processor unit (в дальнейшем просто GPGPU). 1.1 Преимущества GPGPU. Современные GPU обладают высокой скоростью доступа к своей собственной оперативной памяти, которая обычно именуется текстурной памятью и имеют высокую вычислительную мощность. Для примера приведем некоторые числа (см. [2]): Если у Intel 3.0 GHz Pentium 4 пиковая вычислительная мощность оценивается как 12 GFLOPs, то у видеокарты ATI Radeon X1800XT 120 GFLOPs, для этого же оборудования пиковая скорость обмена данными между процессором и соответствующей памятью составляет соответственно 5.96 GB/s у CPU против 42 GB/s у GPU. Но GPU не только быстры и мощны, но и что важно, их мощности растут очень быстрыми темпами. Так в [3] приводятся следующие данные : годовой рост производительности CPU - 1.4 раза, а для GPU - 1.7 раз для фрагментного процессора и 2.3 раза для вершинного. Figure by Courtesy Ian Buck and John Owens, Stanford University. Если учесть при этом относительно низкую стоимость видеокарт по сравнению с равномощными CPU то станет понятно зачем использовать видеокарты для вычислений общего назначения. 1.2 Программируемость и гибкость. Современные языки шейдеров разработанные с использованием синтаксиса C позволяют программистам относительно быстро начать писать эффективный программы для графического ускорителя. При этом язык шейдеров является достаточно гибким и удобным средством для разработки в том числе и сложных вычислительных подпрограмм, к тому же многие математические функции реализованы на GPU аппаратно, что добавляет быстродействия вычислительным программам. 1.3 Сложности и Ограничения. Причина вычислительной мощности GPU в специализированной под графические приложения высоко параллельной архитектуре. Но это оказывает определенные неудобства на использование GPU для не традиционных задач – сохраняются многие термины и некоторый синтаксис, который может показаться программистам не связанным с компьютерной графикой несколько искусственным. Ввиду специфичности архитектуры GPU (потоковая модель, а не фон Неймана, как на CPU), программирование GPU оказывается трудоемким для многих программистов. Так же сама многопроцессорная архитектура сама по себе уже несет достаточно весомые ограничения на эффективную реализацию многих классов задач. Поэтому говоря о применимости GPGPU для задачи как правило имеется ввиду возможность распараллелить задачу на почти произвольное количество процессоров – от 4 до 32 а дальше возможно и большее количество процессоров. Существенные ограничения несет такие аспекты как наличие только floating point with single precision аппаратно. На последних моделях и на профессиональных видеокартах аппаратно поддерживаются и целые числа, но пока распространенность такого оборудования не велика. 2. Архитектура GPU. 2.1 Конвейер GPU. Архитектура GPU определилась благодаря специфическим потребностям компьютерной графики. Именно это определило высокий параллелизм архитектуры и ее специфику, благодаря специфической архитектуре GPU выполняют поставленные перед ними задачи гораздо эффективнее чем классические процессоры. Структура проведения операций на всех современных GPU может быть представлена конвейером (graphic pipeline), который устроен таким образом, чтобы обеспечить максимальную эффективность выполнения задач компьютерной графики при условии потоковой параллельной архитектуры. На вход подается набор вершин, поступающий из приложения затем вершины обрабатываются в вершинных процессорах классифицируемых как MIMD, программа для вершинных процессоров называется вершинным шейдером. После этого результат работы вершинного шейдера поступает на сборку примитивов, таких как полигоны или линии, далее следует тесты видимости отсечения и прочие стандартные операции компьютерной графики, после них данные поступают на растеризацию. Здесь объемное изображение проецируется на плоский экран, и полученная картинка масштабируется в соответствии-с параметрами окна приложения. Результатом этой операции является картинка те текстура представляющая из себя прямоугольный массив так называемых пикселей, пиксель может быть представлен 1-4 числами например в формате RGBA, те red, green, blue, alpha (коэфицент прозрачности). Эта текстура обрабатывается в пиксельных процессорах которые по классификации многопроцессорных систем могут быть классифицированы как SIMD. Затем следует еще несколько операций, и данные поступают в буфер кадра, который собственно и является той картинкой, которую потребитель видит на экране. Ниже приведена схема этого конвейера, конечно данное здесь описание не претендует на полноту, но для целей GPGPU этого вполне достаточно. Figure by [4.]. Excerpted from GPU Gems 2 Copyright 2005 by NVIDIA Corporation. И вершинные и фрагментные процессоры имеют общую память, которая на видеокарте именуется текстурной памятью. В шейдере разработчик имеет доступ на чтение текстурной памяти, но не на непосредственную запись. 2.2 Введение в модель программирования на GPU. Как уже говорилось, GPU построена по принципу потоковой модели вычислений. Сама по себе эта модель не нова и используется очень давно и в последнее время все более широко, поэтому я не буду описывать, что такое сама по себе потоковая модель вычислений, вся информация может быть найдена в специальной литературе. Вместо этого я опишу то как, эта модель представлена на GPU, что позволит излагать материал без излишней теоретизированности. Замечу, что в силу своего устройства фрагментный процессор больше подходит для целей GPGPU, чем вершинный, так же фрагментных процессоров, как правило, в несколько раз больше чем вершинных. Большинство программ использующих технологию gpgpu написаны именно под фрагментные процессоры. Опишу основные общие моменты структуры программы использующей описываемую технологию : 1. Разработчик определяет модель параллелизуемых процессов. Он разрабатывает вычислительное ядро программы, те ту часть программного кода, которая выполняет основные вычисления, именно эта часть и пишется непосредственно для видеокарты, для чего используется язык шейдеров. Каждое вычислительное ядро представляет собой набор команд языка шейдеров, причем каждое ядро работает независимо от другого, на вход шейдера поступают такие данные как текстуры (только на чтение) скаляры массивы чисел, на выход подается один тексел (максимум 4 float числа ) и глубина ( 1 float число ). Результат работы каждого ядра не может быть использован другим ядром, те данные на выходе только на запись. Другими словами данные текстуры на выходе образуют потоки, параллелизм которых обеспечивается ядром выполняемым на каждом процессоре независимо от другого. 2. После того как ядра и потоки были определены, необходимо задать поток выходных данных. Для этого необходимо сделать несколько необычные для программиста не связанного с компьютерной графикой действия, а именно нарисовать некоторый графический примитив на экран средствами используемого графического API. Для целей gpgpu обычно рисуется четырехугольник величиной с используемое окно, которое лучше назвать просто буфером кадра, т.к. собственно окно в привычном смысле не создается, т.е. на экран ничего не выводится, но выделяется область памяти на GPU, предназначенная для хранения результатов вычислений, эта область является ничем иным как буфером кадра в привычной терминологии компьютерной графики. Здесь же отмечу, что память на видеокарте не линейно адресуема, но двумерна и, хотя можно использовать одномерные тектуры, а так же трехмерные, все же наиболее быстрая работа обеспечивается именно для двумерных текстур, так же одно- и трехмерные текстуры существенно ограничены по размеру. 3. После того как вами был нарисован примитив, растерайзер разбивает его на фрагменты, причем если четырехугольник размером с окно, то каждому пикселю соответствует единственный фрагмент. 4. Фрагменты поступают в фрагментные процессоры и обрабатываются там фрагментным (он же пиксельный) шейдером. 5. В итоге мы имеем некоторые данные в буфере кадра которые можно копировать в текстуру если тектуры не была определена как буфер кадра, что позволяют сделать некоторые расширения API например для OpenGL. Далее данные, хранимые в текстуре могут быть переданы в оперативную память или поступить на вход шейдера для дальнейших вычислений. 2.3 Модель памяти GPU. Графические процессоры имеют свою собственную иерархию памяти, во многом схожую с применяемой на обычных процессорах, но все отличную от обычной, т.к. архитектура памяти GPU создавалась из расчета на максимальное ускорение работы алгоритмов компьютерной графики. GPU так же как CPU имеет свои собственные регистры и кэш, для ускорения доступа к данным во время вычислений. Помимо этого GPU имеет свою собственную основную память (графическая память), поэтому для того, чтобы программист мог выполнять вычисления на GPU он должен предварительно передать данные из оперативной памяти CPU в память GPU. Эта операция традиционно является достаточно дорогостоящей с точки зрения производительности ввиду относительно не высокой скорости передачи данных между памятью приложения и памятью видеокарты, хотя современны шины PCI Express и специальные чипы на материнской плате ( такие как NV3 и NV4 ) заметно ускорили этот процесс. Тут стоит отметить так же статью [6.] где описаны некоторые интересные особенности передачи и хранения данных на обоих видах памяти и даны рекомендации по ускорению этого процесса. В отличие от памяти CPU память GPU имеет некоторые серьезные ограничения, и доступ к ней может быть осуществлен только посредством некоторых абстракций графического программного интерфейса. Каждая такая абстракция может мыслиться как тип потока со своим собственным набором правил доступа. Таких потоков непосредственно доступных для программиста может быть выделено 4 – потоки вершин потоки фрагментов потоки буфера кадров потоки текстур. За дополнительной информацией можно обратиться к [5.]. 2.4 Контроль потоков на GPU. //…. 3. Система программирования для GPU. Под термином система программирования будем понимать следующую совокупность средств разработки язык программирования высокого уровня, система ля отладки кода, профилировщик. Существует множество языков программирования шейдеров, дебагеров и профилировщиков, но большинство из них не удобны в работе, узконаправленны под конкретный язык шейдеров или даже платформу, а некоторые из них не подходят для целей gpgpu. В итоге мы имеем не так уж много средств для разработки, отладки и оптимизации приложений под GPU. 3.1 Языки программирования шейдеров высокого уровня. Большинство языков шейдеров высокого уровня созданы таким образом, что бы максимально отвечать требованиям компьютерной графики, поэтому, оперируя с массивами или векторами, или просто входными данными, мы употребляем синтаксис с названиями, характерными для компьютерной графики и обработки изображений. Например, доступ к компонентам вектора есть то же, что доступ к компонентам цвета rgba, в некоторых языках шейдеров мы должны явно указывать квалификаторы после входных параметров, например :COLOR0 или что-нибудь в этом роде, т.е. указываем тип регистровой переменной в которой хранятся входные данные для шейдера. Большинство языков шейдеров высоко уровня основаны на синтаксисе С, но с существенными ограничениями и как правило более жесткой типизацией. Наиболее известны языки GLSL ( OpenGL Shading Language ), HLSL ( High Level Shading Language), Cg( C for graphics by NVidia Corp.), они же и используются для gpgpu. Есть так же и другие языки шейдеров например одним из первых языков шейдеров высокого уровня является Render Man. Так же стоит отметить Brook programming language расширяющий стандартный С с использованием потоковой модели вычислений, этот язык был разработан специально для обеспечения вычислений на gpu, но используется не часто ввиду того, что поддерживается не так хорошо как вышеперечисленные языки. Все три вышеперечисленных языка шейдеров поддерживают операции работы с 4х компонентными векторами и матрицами, вектора больших размерностей ан уровне языка не поддерживаются. Вообще процессоры gpu имеют векторную архитектуру для наилучшей работы с пикселями, которые представляют собой вектора с количеством компонент, не превосходящем 4. Так же все они имеют богатый набор встроенных математических функций, таких как sin, cos, tg, ln и прочие. Некоторые из них (тригонометрические), на современных gpu поддерживаются аппаратно. Наиболее популярны у программистов gppgu языки Cg и GLSL ввиду того, что первый активно поддерживается NVidia и имеется много кода написанного на нем, в частности код к книгам GPU Gems, GPU Gems2, которые являются наиболее интересными и содержательными нетривиальными книгами по компьютерной графике вообще. GLSL, поставляемый вместе с OpenGL, является весьма популярным языком у всех разработчиков, использующих данный API. Замечу, что Cg работает как под OpenGL, так и под Direct3D от Microsoft. Ввиду того, что в научном софте Direct3D почти не применяется, gpgpu код написан в основном на Cg или GLSL. Более подробную информацию о различных языках шейдерах и об их развитии вы можете найти в [7.], которая так же была издана на русском языке. Так же эта книга в первую очередь наиболее полный учебник по GLSL. 3.2 Средства отладки. //… 3.3 Профилировщики. //… 4. Техники GPGPU. Не смог найти соответствующих терминов на русском языке, поэтому использую английскую терминологию. 4.1 Map. Самой очевидной операцией gpgpu является так называемый map operation. На вход этой операции подается входной поток данных и функция, map применяет эту функцию к каждому элементу потока. Те происходит то что описывалось в предыдущих разделах. Результат выполнения фрагментного шейдера и есть результат применения этой операции. 4.2 Уменьшение входного потока (reducing). Иногда возникает необходимость получить поток меньшей размерности чем подаваемый на входе, возможно даже одноэлементный поток. Такая техника вычислений называется reducing. Она применяется например при нахождении максимального элемента во входном потоке ил подсчета суммы всех элементов потока. //… 4.3 Scatter and Gather Если запись или чтение из памяти осуществляется косвенно то эти операции именуются Scatter and Gather соответственно. Scatter operation выглядит как обыкновенный код на С : data[i] = v , где v будет храниться в массиве data по индексу i. Gather операция выглядит аналогично : v = data[i]. Реализация последней операции на GPU представляет из себя вариации на тему чтения элементов текстуры. Чтение из текстуры d с использованием вычисленных координат представляющее из себя косвенное чтение из тектуры определяет gather. Представление scatter не столь простое. Дело в том что фрагменты имеют четкое местоположение в буфере кадров, из-за этого нет никакой возможности производить запись по произвольному адресу памяти на GPU. Те scatter не реализованно на GPU. Поэтому программистам приходится прибегать к всевозможным трюкам для реализации scatter: изменение scatter на gather(см .[8.] )маркировка выходных данных фрагментного шейдера, а затем их сортировка в соответствии с маркировкой; использование вершинных шейдеров для обеспечения scatter. 4.4 Фильтрация потока. Многие алгоритмы требуют возможности выбора из потока некторого подмножества данных и отбросу остальных. Фильтрация потока- по существу неоднородная reduction. Эта техника не может быть основана на стандартном механизме reduction, тк размер данных после фильтрации заранее не известени может варьироваться. Horn описал в [9.] технологию назваееую stream compaction которая представляет из себя фильтрацию потоков на GPU. С использованием сканирования и поиска фильтрация потока требует O(logn) операций. 4.5 Сортировка Сортировка является классической задачей программирования и существует множество алгоритмов эффективно ее решающих. Большинство этих алгоритмов требуют scatter и обладают зависимостью от данных эти операции эффективно не реализуются на GPU поэтому нам нужны другие алгоритмы который бы подходили для нашей модели вычислений. Большинство придуманных алгоритмов сортировки для GPU базируются на сортировочных сетях (sorting networks). Основная идея таких сортировок в том что задается конфигурация сети которая сортирует данные за фиксированное количество шагов. Смысл задания сети в том что тогда проблема решается в терминах gather, а значение фиксированности количества шагов для конкретного размера входных данных в том что в таком случае нет необходимости в условных переходах в программе (соответствующие инструкцие имеются они все же являются относительно медленными). Эффективная GPU сортировка требует O(n (log n)(log n)) операций, что больше log n раз чем того требут например быстрая сортировка. 7. Литература. Эта обзорная статья написана в основном на основании [1.], оттуда взята структура статьи. Что касается модели памяти, в основном информация почерпнута из [5.]. Ссылки на другие источники приводятся в тексте. 1. John D. Owens, David Luebke, Naga Govindaraju, Mark Harris, Jens Kruger, Aaron E. Lefohn, and Timothy J. Purcell. A Survey of General-Purpose Computation on Graphics Hardware. EUROGRAPHICS 2005. 2. MikeHouston. General Purpose Computation on Graphics Processors. 3. David Luebke. General-Purpose Computation on Graphics Hardware. University of Virginia. 4. John Owens. GPU Gems 2. Chaper 29: Streaming Architectures and Technology Trends. University of California, Davis. NVidia corporation. 5. John Owens, Aaron Lefohn, Joe Kniss. GPU Gems 2. Chaper 33: Implementing Efficient Parallel Data Structures on GPUs. University of California, University of Utah. NVidia corporation. 6. Ikrima Elhassan. Technical Brief : Fast Texture Downloads and Readbacks using Pixel Buffer Objects in OpenGL. Nvidia corporation, 2005. 7. Randi J. Rost . OpenGL® Shading Language. Addison Wesley, 2004. 8. BUCK I. GPGPU: General-purpose computation on graphics hardware—GPU computation strategies & tricks. ACM SIGGRAPH Course Notes (Aug. 2004). 9. HORN D. Stream reduction operations for GPGPU applications. In GPU Gems 2, Pharr M., (Ed.). Addison Wesley, Mar. 2005, ch. 36.