Будущее программирования аппаратных ускорителей

Многие из новейших суперкомпьютеров основаны на аппаратных ускорителях вычислений (accelerator). включая две самые быстрые системы согласно TOP500 от 11/2013. Ускорители распространяются так же и на обычных PC и даже появляются в портативных устройствах, что ещё больше способствовует росту интереса к программированию ускорителей.

Такое широкое применение ускорителей является результатом их высокой производительности, энергоэффективности и низкой стоимости. Например, если сравнить Xeon E5-2687W и GTX 680, выпущенные в марте 2012, мы увидим, что GTX 680 в четыре раза дешевле, имеет в 8 раз большую производительность операций одинарной точности и в 4 раза большую пропускную способность памяти, а так же обеспечивает более 30 раз большую производительность в пересчёте на доллар и в 6 раз большую производительность на ватт. Исходя из таких сравнительных результатов, ускорители должны бы использоваться везде и всегда. Почему же этого не происходит?

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

Изначальные попытки использовать GPU, которые в настоящее время являются наиболее известным видом ускорителей, для ускорения неграфических приложений были громоздкими и требовали представления вычислений в виде шейдерного кода, который поддерживал ограниченный поток управления и не поддерживал целочисленный операции. Постепенно эти ограничения были сняты, что способствует более широкому распространения вычислений на графических чипах и позволяет специалистам из не графических областей программировать их. Важнейший шаг в этом направлении был предпринят с выходом языка программирования CUDA. Он расширяет C/C++ при помощи дополнительных спецификаторов и ключевых слов, а так же библиотек функций и механизма запуска частей кода, называемых ядра (kernel), GPU.

Скорое принятие CUDA в сочетании с тем, что это проприетарный продукт а так же сложностью написания качественного кода на CUDA, приводит к созданию других подходов к программированию ускорителей, в том числе OpenCL, C++ AMP и OpenACC. OpenCL является непроприетарным двойником CUDA и пользуется поддержкой многих крупных компаний. Он не ограничен только чипами NVidia, а так же поддерживает графические процессоры AMD, многоядерные CPU, MIC (Intel Xeon Phi), DSP и FPGA, что делает его переносимым. Однако, так же как и CUDA, он очень низкоуровневый. Требует от программиста непосредственно управлять перемещением данных, требует непосредственно определять места хранения переменных в иерархии памяти и в ручную реализовывать параллелизм в коде. C++ Accelerated Massive Parallelism (C++ AMP) работает на среднем уровне. Она позволяет описывать параллельные алгоритмы уже на самом С++ и скрывает весь низкоуровневый код от программиста. Оператор «for each» инкапсулирует параллельный код. C++ AMP привязан к Windows, пока ещё не поддерживает CPU и страдает от больших накладных расходов запуска, что делает практически нецелесообразным ускорение с его помощью кода, выполняющегося кратковременно.

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

Чтобы понять как в дальнейшем будет развиваться область программирования аппаратных ускорителей стоит изучить как подобный процесс протекал в прошлом с другими аппаратными ускорителями. К примеру, ранние продвинутые PC имели дополнительный процессор — сопроцессор, выполняющий вычисления с плавающей точкой. Позже он был объединён на кристалле с центральным процессором — CPU — и сейчас является его неотъемлемой частью. Они имеют лишь разные регистры и арифметико-логические устройства (АЛУ). Более поздние SIMD-расширения процессоров (MMX, SSE, AltiVec и AVX) не выпускались в виде отдельных чипов, но сейчас они так же полностью интегрированы в ядро процессоров. Так же как и операции с плавающей точкой, SIMD-инструкции вычисляются на отдельных АЛУ и с использованием собственных регистров.

Удивительно, но два этих типа инструкций существенно отличаются с точки зрения программиста. Вещественные типы и операции с ними были давно стандартизированы (IEEE 754) и сегодня используются повсеместно. Они доступны в высокоуровневых языках программирования через обычные арифметические операции и встроенные вещественные типы данных: 32 бит для вещественных чисел одинарной точности и 64 бит для двойной точности. Напротив, не существует никаких стандартов для SIMD-инструкций и само их существование во многом скрыто для программиста. Использование этих инструкций для векторизации вычислений делегировано компилятору. Разработчики, желающие явно использовать эти инструкции должны обращаться к компилятору при помощи специальных не кроссплатформенных макросов.

Поскольку производительность GPU и MIC ускорителей обусловлена их SIMD природой, мы думаем, что их развитие пойдёт путём предыдущих SIMD-ускорителей. Ещё одно сходство с SIMD и ключевая особенность CUDA, сделавшая её успешной, то что CUDA скрывает SIMD сущность, характерную для графических процессоров и позволяет программисту мыслить в терминах потоков, оперирующих скалярными данными, нежели в терминах варпов (warp), оперирующих векторами. Поэтому несомненно, ускорители так же будут перенесены на кристалл с процессором, но мы полагаем, что их программный код не будет достаточно вшит в обычный CPU-код так же как и аппаратные типы данных GPU не будут напрямую доступны программистам.

Некоторые ускорители уже были совмещены на кристалле с традиционными процессорами, это AMD APU (используется в Xbox One), процессоры Intel с интегрированной HD-графикой и Tegra SoC от NVIDIA. Однако, ускорители останутся, вероятно, отдельным ядром потому что трудно объединить их с традиционным процессорным ядром до такой же степени, как это было сделано с математическим сопроцессором и с SIMD-расширениями, то есть, урезать до набора регистров и отдельного АЛУ в составе центрального процессора. В конце концов, ускорители такие быстрые, параллельные и энергоэффективные как раз по причине отличных от CPU архитектурных решений, таких как несвязный кэш, совершенно другая реализация конвейера, память GDDR5 и на порядок большие количество регистров и многопоточность. Следовательно, сложность запуска кода на акселераторах по прежнему останется. Так как даже процессорные ядра, выполненные на одном кристалле как правило имеют общие лишь нижние уровни иерархии памяти, поэтому скорость обмена данными между CPU и акселераторами возможно будет расти, но по-прежнему останется узким местом.

Необходимость явного управления процессами обмена данными между устройствами является существенным источником ошибок и тяжким бременем ложится на программистов. Часто бывает для небольших алгоритмов, что больше кода приходится писать, чтобы организовать обмен данными, нежели сами вычисления над ними. Устранение этого бремени является одним из основных преимуществ высокоуровневых подходов к программированию, таких как C++ AMP и OpenACC. Даже низкоуровневые реализации направлены на решение этой проблемы. К примеру, хорошо отлаженный и унифицированный доступ к памяти это одно из основных улучшений, которые проводятся в последних версиях CUDA, OpenCL и аппаратных решениях NVIDIA GPU. Всё же для достижения хорошей производительности обычно требуется помощь программиста, даже в очень высокоуровневых решениях таких как OpenACC. В частности, выделение памяти в требуемых местах и перенос данных часто нужно выполнять вручную.

К сожалению, все упрощения, предлагаемые такими подходами могут оказаться только частичным решением. Учитывая, что будущие процессоры будут близки к сегодняшним (малым) суперкомпьютерам, вполне вероятно, что в них будет больше ядер, чем сможет обслуживать их общая память. Вместо этого мы думаем, что на каждом кристалле будут кластеры из ядер и каждый кластер будет иметь собственную память, возможно расположенную над этими ядрами в трёхмерном пространстве. Кластеры будут связаны друг с другом сетью выполненной на том же кристалле используя протокол наподобие MPI. И это не так далеко от истины потому что Intel только что анонсировала, что в будущие чипы Xeon будут добавлены сетевые функции, и это шаг в данном направлении. Следовательно, вполне вероятно, что чипы в будущем станут всё более гетерогенными объединяя ядра, оптимизированные по задержкам и пропускной способности; сетевые адаптеры, центры сжатия и кодирования, FPGA и т. д.

Это поднимает крайне важный вопрос о том, как программировать такие устройства. Мы полагаем, что ответ на этот вопрос удивительно схож с тем, как это решено на сегодняшний день для многоядерных CPU, SIMD расширений и существующих на данный момент аппаратных ускорителей. Происходит это на трёх уровнях, которые мы называем библиотеки, инструменты автоматизации и «сделай сам». Библиотеки — простейший подход, основанный на простом вызове функций из библиотеки, которая кем-то уже оптимизирована для ускорителя. Многие современные математические библиотеки относятся к этому классу. Если большинство вычислений программы выполняется в этих библиотечных функциях, то применение этого подхода будет вполне оправдано. Он позволяет нескольким специалистам написать одну хорошую библиотеку для ускорения множества приложений, в которых эта библиотека будет использована.

В C++ AMP и OpenACC используется другой подход — инструменты автоматизации. При таком подходе тяжёлая работа перекладывается на компилятор. Успех его зависит от качества и сложности существующих программных инструментов и, как было сказано, часто требует вмешательства программиста. Тем не менее, большинство программистов могут довольно быстро достичь хороших результатов используя этот подход, который не ограничен только использованием предопределённых функций из библиотек. Это похоже на то, как несколько групп специалистов реализует «внутренности» SQL, что позволяет обычным разработчикам в дальнейшем пользоваться готовым оптимизированным кодом.

Наконец, подход «сделай сам» используется в CUDA и OpenCL. Он даёт программисту полный контроль над доступом почти ко всем ресурсам ускорителя. При хорошей реализации, результирующий код превосходит по производительности любой из двух предыдущих. Но это даётся путём значительных усилий на изучение этого подхода, написания большого количество дополнительного кода и большего простора для возможных ошибок. Всяческие усовершенствования сред разработки и отладки могут смягчить все эти трудности, но только до определённой степени. А значит этот подход полезен в первую очередь для экспертов. Таких, которые как раз занимаются разработкой методов, упомянутых в предыдущих двух подходах.

Из-за простоты использования библиотек, программисты способны использовать его где это только возможно. Но это возможно только если соответствующие библиотечные функции существуют. В популярных областях такие библиотеки обычно существуют. Например, операции с матрицами (BLAS). Но в смежных областях или там, где вычисления не структурированы, реализовать библиотеки ускорители трудно. При отсутствии соответствующих библиотек, программисты выбирают инструменты автоматизации если конечно они достаточно развиты. Вычисления недоступные в виде библиотек, не очень требовательные к производительности и поддерживаемые компилятором, скорее всего реализуются с использованием инструментария автоматизации. В остальных случаях используется метод «сделай сам». Поскольку OpenCL объединяет успешные решения, представленные в CUDA, не является проприетарным и поддерживает разные аппаратные решения, мы думаем, что он или производные от него решения станут преобладающими в этой области подобно тому, как MPI стал стандартом де-факто в программировании систем с распределённой памятью.

Принимая во внимание аппаратные особенности и процесс эволюции, изложенный выше, можно говорить, что будущие процессорные чипы будут содержать множество кластеров с собственной памятью. Каждый кластер будет состоять из многих ядер, при этом не обязательно все ядра будут функционально идентичными. Каждое многопоточное ядро будет состоять из множества вычислительных звеньев (т. е. функциональных блоков или АЛУ) и каждое вычислительное звено будет выполнять SIMD-команды. Даже если будущие чипы не будут включать всё это сразу, все они будут иметь одно ключевое сходство, а именно, иерархию уровней параллелизма. Для создания эффективных и переносимых программ для подобных систем, мы предлагаем то, что мы называем техникой «обширного параллелизма» («copious-parallelism» technique). Она является обобщением того, как MPI-программы адаптируются программистом под разное количество вычислительных узлов или как код OpenMP неявно приспосабливается по разное количество ядер (cores) или потоков (threads).

Основная идея обширного параллелизма и причина такого названия в том, чтобы обеспечить обширные, параметризируемые возможности параллелизма на каждом уровне. Параметризация позволит понизить степень параллелизма программы на любом уровне для приведения её в соответствие со степенью аппаратного параллелизма на этом уровне. К примеру, на системе с разделяемой памятью не требуется самый высокий уровень параллелизма, его надо установить в один «кластер». Аналогично, в ядре, где вычислительные звенья не могут выполнять SIMD-инструкции, параметр, определяющий ширину SIMD должен быть установлен в единицу. Пользуясь подобной техникой можно реализовывать возможности многоядерных CPU, GPU, MIC и других устройств, так же как и вероятных будущих аппаратных архитектур. Писать программы таким образом бесспорно труднее, но обширный параллелизм позволяет при помощи единственной кодовой базы извлечь высокую производительность из широкого класса устройств.

Мы протестировали этот подход на задаче прямого моделировании n-тела. Мы написали единственную реализацию алгоритма с обширным параллелизмом с помощью OpenCL и произвели замеры на четырёх совершенно разных аппаратных архитектурах: NVIDIA GeForce Titan GPU, AMD Radeon 7970 GPU, Intel Xeon E5-2690 CPU и Intel Xeon Phi 5110P MIC. Учитывая, что 54% всех операций с плавающей точкой были не FMA-операциями (FMA — операции умножения с накоплением), обширный параллелизм позволил достичь производительности 75% от теоретической пиковой для NVIDIA Titan, 95% для Radeon, 80,5% для CPU и 80% для MIC. Это только отдельный пример, но результаты его весьма обнадёживающие. На самом деле, мы считаем, что обширный параллелизм будет в течение некоторого времени единственным подходом к написанию переносимых высокопроизводительных программ для существующих и будущих систем на аппаратных ускорителях.

[ Источник ]
9 января 2014
Камиль Роки (Kamil Rocki) и Мартин Бёртшер (Martin Burtscher)

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 25

    +3
    Ускорители от AMD сейчас очень даже хорошо используются под OpenCL, попробуйте найти в продаже R9 280X по нормальной цене :)
      0
      GPU ускорители много где используются для рассчётов. Из достаточного серъёзного — например, для высокочастотных торгов на бирже (я видел реализации на NVidia CUDA).

      А дефицит в каких-то регионах не является главным показателем. Это скорее показатель поведения поставщиков, которые почему-то не удовлетворяют спрос.
      Например, R9 280X есть в продаже на NewEgg, Overstock, Amazon. На каждом сайте есть порядка десятка разных моделей. Высылают в тот же день.
        +1
        Если Вы имеете ввиду, что покупать её именно для GPGPU редко бывает оправдано, то скорее всего да. Но всё же, идея запуска параллельных частей кода на множестве разных устройств. хороша.
          0
          Наоборот, я думаю что это оптимальный вариант для GPGPU с производительностью свыше 95% от теоретической и повышенный спрос на эти карты сейчас обусловлен именно их использованием в качестве «числодробилок» для майнинга криптовалют, где их покупка более чем оправдана.
            +1
            В этом вопросе не могу быть уверенным, но это интересный факт. Всегда было интересно сколько нужно потратить на железо чтобы bitcoin-майнинг был оправданным.
              0
              сколько нужно потратить на железо чтобы bitcoin-майнинг был оправданным

              Практически без разницы, запустить 1 ускоритель или 100 одинаково оправдано, просто 100 принесут в 100 раз больше прибыли.

              Если не брать в расчет экономию от использования систем из нескольких видеокарт, то время окупаемости 1 карты или 100 будет одинаковое, и зависит только от колебаний курса и сложности выбранной для майнинга валюты.
        +1
        Спасибо за интересную статью.
        Предполагается ли автоматическая параметризация запущенного приложения в зависимости от доступных ресурсов (например у одного пользователя есть MIC, а у другого только GeForce)?
          0
          Автоматическая параметризацию одного и того же кода под любые ускорители — ключевая идея обширного параллелизма и его реализации в OpenCL. MIC, GeForce или вообще только CPU — работать будет всегда: просто выбираем желаемое устройство из списка — и вперед. Системы из нескольких ускорителей (одинаковых или разных), впрочем, в OpenCL уже требуется адаптировать. Кроме того, хотя OpenCL-код запустится везде, его оптимизация может быть зависимой от устройства и плохо переноситься (скажем на одном виде ускорителей будет 90% от теоретической пиковой скорости, на других — всего 40%). Плюс эта портабельность требует включения исходного кода CL-ядер в состав программы (тогда он компилируется на лету драйвером аналогично шейдерам), и кто-то может предпочесть распространять сугубо бинарники, заточенные под какую-то конкретную платформу
          +1
          Скоро круг замкнётся.

          В 1992 году, когда компьютеры уже стали достаточно проворны, чтобы работать с 3D-графикой, появилось 2 стандарта — OpenGL и Reality Lab, который впоследствии был переименован в Direct3D. В 1995 году началась гонка стандартов. Сперва активно побеждала Microsoft, так как у нее был козырь — Windows был монополистом на десктопах.

          Но графику на процессоре рисовать очень тяжко. Одно ядро, считающее перемножение матриц 4x4 последовательно, было загружено на проценты от своих возможностей и работало ужасно медленно. Чтобы исправить это, сперва появился Intel MMX. Потом AMD 3DNow! Потом SSE… Но даже возможность перемножать матрицы одной инструкцией не решала проблему. Игры стали шустрее и красивее, но расчет светотени по-прежнему включал только «пластик».

          Компании решили добавлять процессоры с множеством примитивных ядер в видеокарты. Первая карточка Voodoo появилась раньше, чем у кого-либо в процессоре домашнего ПК было более одного ядра. А в ней ядер уже было несколько…

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

          Однако людям хотелось видеть хитрую светотень, отражения и много другого, что требовало вмешательства в арифметику расчета. Так появились шейдеры. И этот момент был знаковым — в компьютере стало на одно программируемое устройство больше.

          Шейдеры развивались, пока не стало возможно решать на них серьёзные математические задачи.

          В итоге OpenGL превратился в набор команд по загрузке текстур, геометрии и шейдеров в видяху (я имею в виду OpenGL3). Вы, по сути, просто задаете конвейер и командуете «поехали». Вся программа написана на GLSL.

          И в какой-то момент стало понятно, что всё это великолепие годится не только для графики — параллелизуемых задач куда больше. Так появились CUDA и OpenCL.

          А теперь идет обсуждение того, чтобы вернуть «графический ускоритель» (если его всё еще можно так называть) обратно в процессор. Скоро в наших процессорах будет по 30-50 ядер. Возможно, некоторые из них будут уметь меньше, чем остальные, но это уже не будут видеокарты. Видеокарта снова будет заниматься основным делом — переносить массив в памяти на дисплей.

          Скоро круг замкнётся.

            0
            Я думаю, видеокарты вымрут только вместе с форм-фактором классического ПК.
            Потому что вычислительной мощности никогда не бывает много, а отдельный девайс, занимающий куда больше места, чем процессор, и потребляющий дополнительные 300W, всегда будет в разы мощнее.

            Другие форм-факторы — планшеты, ноутбуки, телефоны — другое дело. Там разумно мощность разменять на мобильность и энергоэффективность.
              0
              Мысль разумная. Но можно ведь и по-другому посмотреть на этот вопрос.

              Стандарт IBM PC, о котором мы с вами говорим — модульный стандарт. И поскольку он, по сути, оброс сложностями, «костылями» и устарел по сути, должен появиться альтернативный модульный стандарт. Вот, например.

              Возможно, имеет смысл рассмотреть архитектуру, в которой CPU — не центр вселенной, а рядовое устройство. И в этой системе-таки разумнее будет не объединять функции вывода графики и параллельного расчета. А существующий OpenGL уйдет в историю, как частный случай OpenCL.
                0
                Стандарт razer не отвечает на проблему, потому что место под видеокарту всё равно зарезервировано, и отказавшись от её установки, не получится из стационарного девайса сделать мобильное.

                Архитектура, где CPU — не центр вселенной — вообще фантастика, с учётом кол-ва софта (прежде всего ОС), спроектированных под неё. Смена парадигмы теоретически возможна, но это дело не одного и не двух десятков лет.
              +1
              Как показал опыт larrabee, софтверные решения не конкурентоспособны, даже имея аппаратные текстурные семплеры и прочее. В последних процессорах Интел, векторные юниты СPU мощней чем у GPU, однако вряд ли захотите использовать первый для 3D графики.
                0
                Какие последние процессоры Вы имеете ввиду?
                    0
                    Но ведь не мощнее они. И как бы там ни было, это не снимает проблему программирования таких гетерогенных систем (на одном кристалле процессоры или в разных устройствах).
                      0
                      4770K быстрее на float и на double даже более чем вдвое.
                        0
                        1 из 4 x86-ядер 4770К против 1 из 2304 CUDA-ядер GTX780?
                          0
                          причём тут GTX780? причём тут тем более CUDA? вы с какой луны упали? =)
                          У 4770K пик у SIMD 448GFlops / 224 в double а у его GPU — GT2 400 и 100 соответственно.
                          о чём я и говорил в комментарии habrahabr.ru/post/209606/#comment_7242720
                            0
                            Теперь я понял этот комментарий. Сначала я подумал, что CPU Intel сравнивается с GPU другого производителя.
                              0
                              Я тоже подумал, что имеется ввиду сравнение CPU и GPU разных фирм. То что векторные операции на CPUработают быстро это на сегдоняшний день хорошее дополнение к производительности современных видеокарт.
                0
                Нельзя видать Xeon Phi и Tesla в одну кучу. У Phi приемущество в отсутствии проблем с branch divergence. CUDA с этой проблемой безусловно борется, но по крайней мере каждое ядро Phi может автономно что-сто считать, а на CUDA такое не возможно.
                  0
                  Алгоритмическая сложность текущих задач позволяет использовать SIMD-расширения именно как ускорители. Как заметил qw1, сегодня трудно представить себе что-то другое. Но вообще это возможно я считаю.
                    0
                    Это на Phi SIMD, а на CUDA SIMT, это несколько другое.
                      0
                      Спасибо за уточнение, буду иметь ввиду.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое