Как использовать процессор видеокарты для вычислений cpu
До сих пор в обсуждении приемов параллельного программирования мы рассматривали только ядра процессора. Мы приобрели некоторые навыки распараллеливания программ по нескольким процессорам, синхронизации доступа к совместно используемым ресурсам и использования высокоскоростных примитивов синхронизации без применения блокировок.
Однако, существует еще один способ распараллеливания программ - графические процессоры (GPU), обладающие большим числом ядер, чем даже высокопроизводительные процессоры. Ядра графических процессоров прекрасно подходят для реализации параллельных алгоритмов обработки данных, а большое их количество с лихвой окупает неудобства выполнения программ на них. В этой статье мы познакомимся с одним из способов выполнения программ на графическом процессоре, с использованием комплекта расширений языка C++ под названием C++ AMP.
Ставим CUDA Toolkit
Идем по вышеприведенной ссылке и скачиваем CUDA Toolkit для Linux (выбрать можно из нескольких версий, для дистрибутивов Fedora, RHEL, Ubuntu и SUSE, есть версии как для архитектуры x86, так и для x86_64). Кроме того, там же надо скачать комплекты драйверов для разработчиков (Developer Drivers for Linux, они идут первыми в списке).
Запускаем инсталлятор SDK:
Когда установка будет завершена, приступаем к установке драйверов. Для этого завершаем работу X-сервера:
Открываем консоль и запускаем инсталлятор драйверов:
После окончания установки стартуем иксы:
Чтобы приложения смогли работать с CUDA/OpenCL, прописываем путь до каталога с CUDA-библиотеками в переменную LD_LIBRARY_PATH:
Или, если ты установил 32-битную версию:
Также необходимо прописать путь до заголовочных файлов CUDA, чтобы компилятор их нашел на этапе сборки приложения:
Все, теперь можно приступить к сборке CUDA/OpenCL-софта.
А в это время…
А пока ты читаешь сей материал, трудяги из процессорных концернов
разрабатывают свои технологии по внедрению GPU в CPU. Со стороны AMD все
понятно: у них есть большущий опыт, приобретенный вместе с ATI.
Творение «микродевайсеров», Fusion, будет состоять из нескольких ядер под
кодовым названием Bulldozer и видеочипа RV710 (Kong). Их взаимосвязь будет
осуществляться за счет улучшенной шины HyperTransport. В зависимости от
количества ядер и их частотных характеристик AMD планирует создать целую ценовую
иерархию «камней». Также планируется производить процессоры как для ноутбуков (Falcon),
так и для мультимедийных гаджетов (Bobcat). Причем именно применение технологии
в портативных устройствах будет первоначальной задачей для канадцев. С развитием
параллельных вычислений применение таких «камней» должно быть весьма популярно.
Intel немножко отстает по времени со своей Larrabee. Продукты AMD,
если ничего не случится, появятся на прилавках магазинов в конце 2009 – начале
2010 года. А решение противника выйдет на свет божий только почти через два
года.
Larrabee будет насчитывать большое количество (читай – сотни) ядер. Вначале
же выйдут продукты, рассчитанные на 8 – 64 ядера. Они очень сходны с Pentium, но
довольно сильно переработаны. Каждое ядро имеет 256 килобайт кэша второго уровня
(со временем его размер увеличится). Взаимосвязь будет осуществляться за счет
1024-битной двунаправленной кольцевой шины. Интел говорит, что их «дитя» будет
отлично работать с DirectX и Open GL API (для «яблочников»), поэтому никаких
программных вмешательств не потребуется.
А к чему я все это тебе поведал? Очевидно, что Larrabee и Fusion не вытеснят
обычные, стационарные процессоры с рынка, так же, как не вытеснят с рынка
видеокарты. Для геймеров и экстремалов пределом мечтаний по-прежнему останется
многоядерный CPU и тандем из нескольких топовых VGA. Но то, что даже
процессорные компании переходят на параллельные вычисления по принципам,
аналогичным GPGPU, говорит уже о многом. В частности о том, что такая
технология, как CUDA, имеет право на существование и, по всей видимости, будет
весьма популярна.
Терминология CUDA
NVIDIA оперирует весьма своеобразными определениями для CUDA API. Они
отличаются от определений, применяемых для работы с центральным процессором.
Поток (thread) – набор данных, который необходимо обработать (не
требует больших ресурсов при обработке).
Варп (warp) – группа из 32 потоков. Данные обрабатываются только
варпами, следовательно варп – это минимальный объем данных.
Блок (block) – совокупность потоков (от 64 до 512) или совокупность
варпов (от 2 до 16).
Сетка (grid) – это совокупность блоков. Такое разделение данных
применяется исключительно для повышения производительности. Так, если число
мультипроцессоров велико, то блоки будут выполняться параллельно. Если же с
картой не повезло (разработчики рекомендуют для сложных расчетов использовать
адаптер не ниже уровня GeForce 8800 GTS 320 Мб), то блоки данных обработаются
последовательно.
Также NVIDIA вводит такие понятия, как ядро (kernel), хост (host)
и девайс (device).
Почему GPU?
Интересно, почему всю вычислительную мощь решили переложить на графический
адаптер? Как видно, процессоры еще в моде, да и вряд ли уступят свое теплое
местечко. Но у GPU есть пара козырей в рукаве вместе с джокером, да и рукавов
хватает. Современный центральный процессор заточен под получение максимальной
производительности при обработке целочисленных данных и данных с плавающей
запятой, особо не заботясь при этом о параллельной обработке информации. В то же
время архитектура видеокарты позволяет быстро и без проблем «распараллелить»
обработку данных. С одной стороны, идет обсчет полигонов (за счет 3D-конвейера),
с другой – пиксельная обработка текстур. Видно, что происходит «слаженная
разбивка» нагрузки в ядре карты. Кроме того, работа памяти и видеопроцессора
оптимальнее, чем связка «ОЗУ-кэш-процессор». В тот момент, когда единица данных
в видеокарте начинает обрабатываться одним потоковым процессором GPU, другая
единица параллельно загружается в другой, и, в принципе, легко можно достичь
загруженности графического процессора, сравнимой с пропускной способностью шины,
однако для этого загрузка конвейеров должна осуществляться единообразно, без
всяких условных переходов и ветвлений. Центральный же процессор в силу своей
универсальности требует для своих процессорных нужд кэш, заполненный
информацией.
Ученые мужи задумались насчет работы GPU в параллельных вычислениях и
математике и вывели теорию, что многие научные расчеты во многом схожи с
обработкой 3D-графики. Многие эксперты считают, что основополагающим фактором в
развитии GPGPU (General Purpose computation on GPU – универсальные
расчеты средствами видеокарты) стало появление в 2003 году проекта Brook GPU.
Создателям проекта из Стэндфордского университета предстояло решить непростую
проблему: аппаратно и программно заставить графический адаптер производить
разноплановые вычисления. И у них это получилось. Используя универсальный язык C,
американские ученые заставили работать GPU как процессор, с поправкой на
параллельную обработку. После Brook появился целый ряд проектов по VGA-расчетам,
таких как библиотека Accelerator, библиотека Brahma, система
метапрограммирования GPU++ и другие.
Предчувствие перспективности разработки заставило AMD и NVIDIA
вцепиться в Brook GPU, как питбуль. Если опустить маркетинговую политику, то,
реализовав все правильно, можно закрепиться не только в графическом секторе
рынка, но и в вычислительном (посмотри на специальные вычислительные карты и
серверы Tesla с сотнями мультипроцессоров), потеснив привычные всем CPU.
Естественно, «повелители FPS» разошлись у камня преткновения каждый по своей
тропе, но основной принцип остался неизменным – производить вычисления
средствами GPU. И сейчас мы подробнее рассмотрим технологию «зеленых» – CUDA
(Compute Unified Device Architecture).
Работа нашей «героини» заключается в обеспечении API, причем сразу двух.
Первый – высокоуровневый, CUDA Runtime, представляет собой функции, которые
разбиваются на более простые уровни и передаются нижнему API – CUDA Driver. Так
что фраза «высокоуровневый» применима к процессу с натяжкой. Вся соль находится
именно в драйвере, и добыть ее помогут библиотеки, любезно созданные
разработчиками NVIDIA: CUBLAS (средства для математических расчетов) и
FFT (расчет посредством алгоритма Фурье). Ну что ж, перейдем к практической
части материала.
Набор для изучения
Практически все аспекты программирования описаны в документации, идущей
вместе с драйвером и двумя приложениями, а также на сайте разработчиков. Размера
статьи не хватит, чтобы описать их (заинтересованный читатель должен приложить
малую толику стараний и изучить материал самостоятельно).
Специально для новичков разработан CUDA SDK Browser. Любой желающий может
ощутить силу параллельных вычислений на своей шкуре (лучшая проверка на
стабильность – работа примеров без артефактов и вылетов). Приложение имеет
большой ряд показательных мини-программок (61 «тест»). К каждому опыту имеется
подробная документация программного кода плюс PDF-файлы. Сразу видно, что люди,
присутствующие со своими творениями в браузере, занимаются серьезной работой.
Тут же можно сравнить скорости работы процессора и видеокарты при обработке
данных. Например, сканирование многомерных массивов видеокартой GeForce 8800
GT 512 Мб с блоком с 256 потоками производит за 0.17109 миллисекунды.
Технология не распознает SLI-тандемы, так что если у тебя дуэт или трио,
отключай функцию «спаривания» перед работой, иначе CUDA увидит только один
девайс. Двуядерный AMD Athlon 64 X2 (частота ядра 3000 МГц) тот же опыт
проходит за 2.761528 миллисекунды. Получается, что G92 более чем в 16 раз
быстрее «камня» AMD! Как видишь, далеко не экстремальная система в
тандеме с нелюбимой в массах операционной системой показывает неплохие
результаты.
Помимо браузера существует ряд полезных обществу программ. Adobe
адаптировала свои продукты к новой технологии. Теперь Photoshop CS4 в полной
мере использует ресурсы графических адаптеров (необходимо скачать специальный
плагин). Такими программами, как Badaboom media converter и RapiHD можно
произвести декодирование видео в формат MPEG-2. Для обработки звука неплохо
подойдет бесплатная утилита Accelero. Количество софта, заточенного под CUDA API,
несомненно, будет расти.
oclHashcat или брутфорс по-быстрому
Как я уже говорил, одними из первых поддержку GPGPU в свои продукты добавили разработчики различных крэкеров и систем брутфорса паролей. Для них новая технология стала настоящим святым граалем, который позволил с легкостью перенести от природы легко распараллеливаемый код на плечи быстрых GPU-процессоров. Поэтому неудивительно, что сейчас существуют десятки самых разных реализаций подобных программ. Но в этой статье я расскажу только об одной из них — oclHashcat.
oclHashcat — это ломалка, которая умеет подбирать пароли по их хэшу с экстремально высокой скоростью, задействуя при этом мощности GPU с помощью OpenCL. Если верить замерам, опубликованным на сайте проекта, скорость подбора MD5-паролей на nVidia GTX580 составляет до 15800 млн комбинаций в секунду, благодаря чему oclHashcat способен найти средний по сложности восьмисимвольный пароль за какие-то 9 минут.
Автор не раскрывает исходники (что, в общем-то, логично), но у программы есть нормально работающая Linux-версия, которую можно получить на официальной страничке.
Далее следует распаковать архив:
$ 7z x oclHashcat-0.25.7z
$ cd oclHashcat-0.25
И запустить программу (воспользуемся пробным списком хэшей и пробным словарем):
$ ./oclHashcat64.bin example.hash ?l?l?l?l example.dict
oclHashcat откроет текст пользовательского соглашения, с которым следует согласиться, набрав "YES". После этого начнется процесс перебора, прогресс которого можно узнать по нажатию . Чтобы приостановить процесс, кнопаем
, для возобновления — . Также можно использовать прямой перебор (например, от aaaaaaaa до zzzzzzzz):
$ ./oclHashcat64.bin hash.txt ?l?l?l?l ?l?l?l?l
И различные модификации словаря и метода прямого перебора, а также их комбинации (об этом можно прочитать в файле docs/examples.txt). В моем случае скорость перебора всего словаря составила 11 минут, тогда как прямой перебор (от aaaaaaaa до zzzzzzzz) длился около 40 минут. В среднем скорость работы GPU (чип RV710) составила 88,3 млн/с.
Рождение GPGPU
Мы все привыкли думать, что единственным компонентом компа, способным выполнять любой код, который ему прикажут, является центральный процессор. Долгое время почти все массовые ПК оснащались единственным процессором, который занимался всеми мыслимыми расчетами, включая код операционной системы, всего нашего софта и вирусов.
Позже появились многоядерные процессоры и многопроцессорные системы, в которых таких компонентов было несколько. Это позволило машинам выполнять несколько задач одновременно, а общая (теоретическая) производительность системы поднялась ровно во столько раз, сколько ядер было установлено в машине. Однако оказалось, что производить и конструировать многоядерные процессоры слишком сложно и дорого. В каждом ядре приходилось размещать полноценный процессор сложной и запутанной x86-архитектуры, со своим (довольно объемным) кэшем, конвейером инструкций, блоками SSE, множеством блоков, выполняющих оптимизации и т.д. и т.п. Поэтому процесс наращивания количества ядер существенно затормозился, и белые университетские халаты, которым два или четыре ядра было явно мало, нашли способ задействовать для своих научных расчетов другие вычислительные мощности, которых было в достатке на видеокарте (в результате даже появился инструмент BrookGPU, эмулирующий дополнительный процессор с помощью вызовов функций DirectX и OpenGL).
Графические процессоры, лишенные многих недостатков центрального процессора, оказались отличной и очень быстрой счетной машинкой, и совсем скоро к наработкам ученых умов начали присматриваться сами производители GPU (а nVidia так и вообще наняла большинство исследователей на работу). В результате появилась технология nVidia CUDA, определяющая интерфейс, с помощью которого стало возможным перенести вычисление сложных алгоритмов на плечи GPU без каких-либо костылей. Позже за ней последовала ATi (AMD) с собственным вариантом технологии под названием Close to Metal (ныне Stream), а совсем скоро появилась ставшая стандартом версия от Apple, получившая имя OpenCL.
FlacCL (Flacuda)
FlacCL — это кодировщик звуковых файлов в формат FLAC, задействующий в своей работе возможности OpenCL. Он входит в состав пакета CUETools для Windows, но благодаря mono может быть использован и в Linux. Для получения архива с кодировщиком выполняем следующую команду:
Далее устанавливаем unrar, mono и распаковываем архив:
$ sudo apt-get install unrar mono
$ unrar x fl accl03.rar
Чтобы программа смогла найти библиотеку OpenCL, делаем символическую ссылку:
$ ln -s $LD_LIBRARY_PATH/libOpenCL.so libopencl.so
Теперь запускаем кодировщик:
$ mono CUETools.FLACCL.cmd.exe music.wav
Сразу скажу, из-за долгого времени инициализации OpenCL заметный выигрыш можно получить только на достаточно длинных дорожках. Короткие звуковые файлы FlacCL обрабатывает почти с той же скоростью, что и его традиционная версия.
KGPU или ядро Linux, ускоренное GPU
Исследователи из университета Юты разработали систему KGPU, позволяющую выполнять некоторые функции ядра Linux на графическом процессоре с помощью фреймворка CUDA. Для выполнения этой задачи используется модифицированное ядро Linux и специальный демон, который работает в пространстве пользователя, слушает запросы ядра и передает их драйверу видеокарты с помощью библиотеки CUDA. Интересно, что несмотря на существенный оверхед, который создает такая архитектура, авторам KGPU удалось создать реализацию алгоритма AES, который поднимает скорость шифрования файловой системы eCryptfs в 6 раз.
Ставим ATI Stream SDK
Stream SDK не требует установки, поэтому скачанный с сайта AMD-архив можно просто распаковать в любой каталог (лучшим выбором будет /opt) и прописать путь до него во всю ту же переменную LD_LIBRARY_PATH:
Как и в случае с CUDA Toolkit, x86_64 необходимо заменить на x86 в 32-битных системах. Теперь переходим в корневой каталог и распаковываем архив icd-registration.tgz (это своего рода бесплатный лицензионный ключ):
$ sudo tar -xzf /opt/AMD-APP-SDK-v2.4-lnx64/icd-registration.tgz -С /
Проверяем правильность установки/работы пакета с помощью инструмента clinfo:
Моделирование движения частиц
Примеры решения задач на графическом процессоре, представленные выше, имеют очень простую реализацию внутреннего цикла. Понятно, что так будет не всегда. В блоге Native Concurrency, ссылка на который уже приводилась выше, демонстрируется пример моделирования гравитационных взаимодействий между частицами. Моделирование включает бесконечное количество шагов; на каждом шаге вычисляются новые значения элементов вектора ускорений для каждой частицы и затем определяются их новые координаты. Здесь распараллеливанию подвергается вектор частиц - при достаточно большом количестве частиц (от нескольких тысяч и выше) можно создать достаточно большое количество заданий, чтобы загрузить работой все ядра графического процессора.
Основу алгоритма составляет реализация определения результата взаимодействий между двумя частицами, как показано ниже, которую легко можно перенести на графический процессор:
Исходными данными на каждом шаге моделирования является массив с координатами и скоростями движения частиц, а в результате вычислений создается новый массив с координатами и скоростями частиц:
С привлечением соответствующего графического интерфейса, моделирование может оказаться очень интересным. Полный пример, представленный командой разработчиков C++ AMP, можно найти в блоге Native Concurrency. На моей системе с процессором Intel Core i7 и видеокартой Geforce GT 740M, моделирование движения 10 000 частиц выполняется со скоростью ~2.5 кадра в секунду (шагов в секунду) с использованием последовательной версии, выполняющейся на обычном процессоре, и 160 кадров в секунду с использованием оптимизированной версии, выполняющейся на графическом процессоре - огромное увеличение производительности.
Прежде чем завершить этот раздел, необходимо рассказать еще об одной важной особенности фреймворка C++ AMP, которая может еще больше повысить производительность кода, выполняемого на графическом процессоре. Графические процессоры поддерживают (часто называемый разделяемой памятью (shared memory)). Значения, хранящиеся в этом кеше, совместно используются всеми потоками выполнения в одной мозаике (tile). Благодаря мозаичной организации памяти, программы на основе фреймворка C++ AMP могут читать данные из памяти графической карты в разделяемую память мозаики и затем обращаться к ним из нескольких потоков выполнения без повторного извлечения этих данных из памяти графической карты. Доступ к разделяемой памяти мозаики выполняется примерно в 10 раз быстрее, чем к памяти графической карты. Иными словами, у вас есть причины продолжить чтение.
Чтобы обеспечить выполнение мозаичной версии параллельного цикла, методу parallel_for_each передается домен tiled_extent, который делит многомерный объект extent на многомерные фрагменты мозаики, и лямбда-параметр tiled_index, определяющий глобальный и локальный идентификатор потока внутри мозаики. Например, матрицу 16x16 можно разделить на фрагменты мозаики размером 2x2 (как показано на рисунке ниже) и затем передать функции parallel_for_each:
Каждый из четырех потоков выполнения, принадлежащих одной и той же мозаике, могут совместно использовать данные, хранящиеся в блоке.
При выполнении операций с матрицами, в ядре графического процессора, взамен стандартного индекса index, как в примерах выше, можно использовать idx.global. Грамотное использование локальной мозаичной памяти и локальных индексов может обеспечить существенный прирост производительности. Чтобы объявить мозаичную память, разделяемую всеми потоками выполнения в одной мозаике, локальные переменные можно объявить со спецификатором tile_static.
На практике часто используется прием объявления разделяемой памяти и инициализации отдельных ее блоков в разных потоках выполнения:
Очевидно, что какие-либо выгоды от использования разделяемой памяти можно получить только в случае синхронизации доступа к этой памяти; то есть, потоки не должны обращаться к памяти, пока она не будет инициализирована одним из них. Синхронизация потоков в мозаике выполняется с помощью объектов tile_barrier (напоминающего класс Barrier из библиотеки TPL) - они смогут продолжить выполнение только после вызова метода tile_barrier.Wait(), который вернет управление только когда все потоки вызовут tile_barrier.Wait. Например:
Теперь самое время воплотить полученные знания в конкретный пример. Вернемся к реализации умножения матриц, выполненной без применения мозаичной организации памяти, и добавим в него описываемую оптимизацию. Допустим, что размер матрицы кратен числу 256 - это позволит нам работать с блоками 16 х 16. Природа матриц допускает возможность поблочного их умножения, и мы можем воспользоваться этой особенностью (фактически, деление матриц на блоки является типичной оптимизацией алгоритма умножения матриц, обеспечивающей более эффективное использование кеша процессора).
Суть этого приема сводится к следующему. Чтобы найти Ci,j (элемент в строке i и в столбце j в матрице результата), нужно вычислить скалярное произведение между Ai,* (i-я строка первой матрицы) и B*,j (j-й столбец во второй матрице). Однако, это эквивалентно вычислению частичных скалярных произведений строки и столбца с последующим суммированием результатов. Мы можем использовать это обстоятельство для преобразования алгоритма умножения матриц в мозаичную версию:
Суть описываемой оптимизации в том, что каждый поток в мозаике (для блока 16 х 16 создается 256 потоков) инициализирует свой элемент в 16 х 16 локальных копиях фрагментов исходных матриц A и B. Каждому потоку в мозаике требуется только одна строка и один столбец из этих блоков, но все потоки вместе будут обращаться к каждой строке и к каждому столбцу по 16 раз. Такой подход существенно снижает количество обращений к основной памяти.
Чтобы вычислить элемент (i,j) в матрице результата, алгоритму требуется полная i-я строка первой матрицы и j-й столбец второй матрицы. Когда потоки мозаике 16x16, представленные на диаграмме и k=0, заштрихованные области в первой и второй матрицах будут прочитаны в разделяемую память. Поток выполнения, вычисляющий элемент (i,j) в матрице результата, вычислит частичное скалярное произведение первых k элементов из i-й строки и j-го столбца исходных матриц.
В данном примере применение мозаичной организации обеспечивает огромный прирост производительности. Мозаичная версия умножения матриц выполняется намного быстрее простой версии и занимает примерно 17 миллисекунд (для тех же исходных матриц размером 1024 х 1024), что в 430 быстрее версии, выполняемой на обычном процессоре!
Прежде чем закончить обсуждение фреймворка C++ AMP, нам хотелось бы упомянуть инструменты (в Visual Studio), имеющиеся в распоряжении разработчиков. Visual Studio 2012 предлагает отладчик для графического процессора (GPU), позволяющий устанавливать контрольные точки, исследовать стек вызовов, читать и изменять значения локальных переменных (некоторые ускорители поддерживают отладку для GPU непосредственно; для других Visual Studio использует программный симулятор), и профилировщик, дающий возможность оценивать выгоды, получаемые приложением от распараллеливания операций с применением графического процессора. За дополнительной информацией о возможностях отладки в Visual Studio обращайтесь к статье «Пошаговое руководство. Отладка приложения C++ AMP» на сайте MSDN.
До сих пор в этой статье демонстрировались примеры только на языке C++, тем не менее, есть несколько способов использовать мощь графического процессора в управляемых приложениях. Один из способов - использовать инструменты взаимодействий, позволяющие переложить работу с ядрами графического процессора на низкоуровневые компоненты C++. Это решение отлично подходит для тех, кто желает использовать фреймворк C++ AMP или имеет возможность использовать уже готовые компоненты C++ AMP в управляемых приложениях.
Я придерживаюсь мнения, что гораздо проще и эффективнее освоить расширение языка (на основе C++ AMP), чем пытаться организовывать взаимодействия на уровне библиотек или вносить существенные изменения в язык IL.
Библиотека Task Parallel Library дает нам уникальную возможность включить в работу все имеющиеся ядра центрального процессора, хотя при этом и придется решать некоторые интереснейшие проблемы синхронизации, чрезмерного дробления задач и неравного распределения работы между потоками выполнения.
Фреймворк C++ AMP и другие многоцелевые библиотеки организации параллельных вычислений на графическом процессоре с успехом можно использовать для распараллеливания вычислений между сотнями ядер графического процессора. Наконец, имеется, неисследованная ранее, возможность получить прирост производительности от применения облачных технологий распределенных вычислений, превратившихся в последнее время в одно из основных направлений развития информационных технологий.
Небольшое резюме
Параллельные вычисления средствами видеокарты – всего лишь хороший инструмент
в руках трудолюбивого программиста. Вряд ли процессорам во главе с законом Мура
придет конец. Компании NVIDIA предстоит пройти еще длинный путь по
продвижению в массы своего API (то же можно сказать и о детище ATI/AMD).
Какой он будет, покажет будущее. Так что CUDA will be back :).
У меня i7-2600, использую самодельную программу для математических вычислений, которая делит объем работ на все доступные потоки (8) и обрабатывает все данные параллельно, тем самым достигается 100% загрузка процессора.
Но так как процесс это требующий очень много времени пришла мысль задействовать еще и видеокарту, у меня GeForce GTX580Ti. Но можно если это будет выгодно можно поменять ее на что нибудь более мощное.
Стоит вопрос:
Можно ли использовать видеокарту для математических вычислений в дополнение к основному процессору или же придется перекладывать все вычисления с CPU на GPU.
На сколько может вырасти производительность при обработки данных с помощью GPU.
Маленький супер-компьютер для вычислений с помощью видеокарт NVIDIA с применением технологии CUDA.
В моей голове созрела идея собрать машину для вычислений с помощью видеокарт NVIDIA с применением.
Использование ресурсов процессора, для физических вычислений PhysX
Здравствуйте, нашел на форуме интересующую тему, но она осталась без ответа.
Совместное использование видеокарт AMD Radeon и NVIDIA
Всем доброе время суток. Прочитал в журнале,что если установлена мощная карта от AMD то в.
Возрасти производительность может, но только при условии, что задача допускает распараллеливание.
Но чудес нет
https://ru.wikipedia.org/wiki/. 0%BB%D0%B0
Недостатки вычислений на видеокарте:
- намного большая сложность программ. Зависимость от "железа", архитектуры видеокарты.
- специфичность. На видеокарте запускается много потоков, но общение между ними затруднено и может серьезно сказаться на производительности. Не все задачи допускают такое независимое распараллеливание.
- заточенность видеокарт под числа формата float32. Этот формат подходит для 3d-графики, но недостаточно точен для научных вычислений (7 знаков).
xxxspeed, зависит от задачи.
Самая главная проблема GPGPU - то что у ГПУ и ЦПУ разная память. Поэтому очень часто ГПУ может посчитать мгновенно, но на трансфер данных туда-сюда съедается всё время, и профита нет от слова совсем. Если вы один раз передаете данные и потом долго-долго считаете, тогда есть смысл.
Ну и если речь идет о GPGPU, то вычисления должны параллелиться на большое число маленьких задач, ибо ядра ГПУ намного слабее ядер ЦПУ, надеюсь вы это знаете.
Самый простой способ - написать библиотеку на C++ AMP и скармливать ей данные. Из плюсов - если вдруг на компьютере не будет установлена видеокарта (или она будет слабая), то автоматически произойдет фоллбек на инструкции AVX/SSE. Очень классная либа, очень жалко, что на дотнет она не портирована. Собственно вот пример использования.
Ну и не забывайте, что существует параллелизм уровня команд и потоков. Это разные вещи, не всегда взаимозаменяемые.
Добавлено через 7 минут
С другой стороны если видеокарта обязательно будет (и она будет нвидиевской), а C++ мы боимся, то можно попробовать библиотечки из этой статьи.
Использование динамических переменных и организации вычислений с помощью обратной блокировки
Использование динамических переменных и организации вычислений с помощью обратной блокировки.
Программа должна отображать ход вычислений, допускать приостановку и прерывания вычислений
Разработать программу для сравнения эффективности двух заданных алгоритмов сортировки путем их.
Софт для видеокарт
Если кто хорошо с этим знаком подскажите какую-нибудь прогу для теста видеокарты.
Разъёмы для видеокарт
Подскажите пожалуйста несколько самых крутых видеокарт которые подойдут на Разъёмы расширения 3.
Введение в C++ AMP
По сути, графический процессор является таким же процессором, как любые другие, но с особым набором инструкций, большим количеством ядер и своим протоколом доступа к памяти. Однако между современными графическими и обычными процессорами существуют большие отличия, и их понимание является залогом создания программ, эффективно использующих вычислительные мощности графического процессора.
Современные графические процессоры обладают очень маленьким набором инструкций. Это подразумевает некоторые ограничения: отсутствие возможности вызова функций, ограниченный набор поддерживаемых типов данных, отсутствие библиотечных функций и другие. Некоторые операции, такие как условные переходы, могут стоить значительно дороже, чем аналогичные операции, выполняемые на обычных процессорах. Очевидно, что перенос больших объемов кода с процессора на графический процессор при таких условиях требует значительных усилий.
Количество ядер в среднем графическом процессор значительно больше, чем в среднем обычном процессоре. Однако некоторые задачи оказываются слишком маленькими или не позволяют разбивать себя на достаточно большое количество частей, чтобы можно было извлечь выгоду от применения графического процессора.
Поддержка синхронизации между ядрами графического процессора, выполняющими одну задачу, весьма скудна, и полностью отсутствует между ядрами графического процессора, выполняющими разные задачи. Это обстоятельство требует синхронизации графического процессора с обычным процессором.
Сразу возникает вопрос, какие задачи подходят для решения на графическом процессоре? Имейте в виду, что не всякий алгоритм подходит для выполнения на графическом процессоре. Например, графические процессоры не имеют доступа к устройствам ввода/вывода, поэтому у вас не получится повысить производительность программы, извлекающей ленты RSS из интернета, за счет использования графического процессора. Однако на графический процессор можно перенести многие вычислительные алгоритмы и обеспечить массовое их распараллеливание. Ниже приводится несколько примеров таких алгоритмов (этот список далеко не полон):
увеличение и уменьшение резкости изображений, и другие преобразования;
быстрое преобразование Фурье;
транспонирование и умножение матриц;
инверсия хеша «в лоб».
Отличным источником дополнительных примеров может служить блог Microsoft Native Concurrency, где приводятся фрагменты кода и пояснения к ним для различных алгоритмов, реализованных на C++ AMP.
C++ AMP - это фреймворк, входящий в состав Visual Studio 2012, дающий разработчикам на C++ простой способ выполнения вычислений на графическом процессоре и требующий лишь наличия драйвера DirectX 11. Корпорация Microsoft выпустила C++ AMP как открытую спецификацию, которую может реализовать любой производитель компиляторов.
Фреймворк C++ AMP позволяет выполнять код на графических ускорителях (accelerators), являющихся вычислительными устройствами. С помощью драйвера DirectX 11 фреймворк C++ AMP динамически обнаруживает все ускорители. В состав C++ AMP входят также программный эмулятор ускорителя и эмулятор на базе обычного процессора, WARP, которые служит запасным вариантом в системах без графического процессора или с графическим процессором, но в отсутствие драйвера DirectX 11, и использует несколько ядер и инструкции SIMD.
А теперь приступим к исследованию алгоритма, который легко можно распараллелить для выполнения на графическом процессоре. Реализация ниже принимает два вектора одинаковой длины и вычисляет поточечный результат. Сложно представить что-либо более прямолинейное:
Чтобы распараллелить этот алгоритм на обычном процессоре, требуется разбить диапазон итераций на несколько поддиапазонов и запустить по одному потоку выполнения для каждого из них. Мы посвятили достаточно много времени в предыдущих статьях именно такому способу распараллеливания нашего первого примера поиска простых чисел - мы видели, как можно это сделать, создавая потоки вручную, передавая задания пулу потоков и используя Parallel.For и PLINQ для автоматического распараллеливания. Вспомните также, что при распараллеливании похожих алгоритмов на обычном процессоре мы особо заботились, чтобы не раздробить задачу на слишком мелкие задания.
Для графического процессора эти предупреждения не нужны. Графические процессоры имеют множество ядер, выполняющих потоки очень быстро, а стоимость переключения контекста значительно ниже, чем в обычных процессорах. Ниже приводится фрагмент, пытающийся использовать функцию parallel_for_each из фреймворка C++ AMP:
Теперь исследуем каждую часть кода отдельно. Сразу заметим, что общая форма главного цикла сохранилась, но первоначально использовавшийся цикл for был заменен вызовом функции parallel_for_each. В действительности, принцип преобразования цикла в вызов функции или метода для нас не нов - ранее уже демонстрировался такой прием с применением методов Parallel.For() и Parallel.ForEach() из библиотеки TPL.
Далее, входные данные (параметры first, second и result) обертываются экземплярами array_view. Класс array_view служит для обертывания данных, передаваемых графическому процессору (ускорителю). Его шаблонный параметр определяет тип данных и их размерность. Чтобы выполнить на графическом процессоре инструкции, обращающиеся к данным, первоначально обрабатываемым на обычном процессоре, кто-то или что-то должен позаботиться о копировании данных в графический процессор, потому что большинство современных графических карт являются отдельными устройствами с собственной памятью. Эту задачу решают экземпляры array_view - они обеспечивают копирование данных по требованию и только когда они действительно необходимы.
Когда графический процессор выполнит задание, данные копируются обратно. Создавая экземпляры array_view с аргументом типа const, мы гарантируем, что first и second будут скопированы в память графического процессора, но не будут копироваться обратно. Аналогично, вызывая discard_data(), мы исключаем копирование result из памяти обычного процессора в память ускорителя, но эти данные будут копироваться в обратном направлении.
Функция parallel_for_each принимает объект extent, определяющий форму обрабатываемых данных и функцию для применения к каждому элементу в объекте extent. В примере выше мы использовали лямбда-функцию, поддержка которых появилась в стандарте ISO C++2011 (C++11). Ключевое слово restrict (amp) поручает компилятору проверить возможность выполнения тела функции на графическом процессоре и отключает большую часть синтаксиса C++, который не может быть скомпилирован в инструкции графического процессора.
Параметр лямбда-функции, index объекта, представляет одномерный индекс. Он должен соответствовать используемому объекту extent - если бы мы объявили объект extent двумерным (например, определив форму исходных данных в виде двумерной матрицы), индекс также должен был бы быть двумерным. Пример такой ситуации приводится чуть ниже.
Наконец, вызов метода synchronize() в конце метода VectorAddExpPointwise гарантирует копирование результатов вычислений из array_view avResult, произведенных графическим процессором, обратно в массив result.
На этом мы заканчиваем наше первое знакомство с миром C++ AMP, и теперь мы готовы к более подробным исследованиям, а так же к более интересным примерам, демонстрирующим выгоды от использования параллельных вычислений на графическом процессоре. Сложение векторов - не самый удачный алгоритм и не самый лучший кандидат для демонстрации использования графического процессора из-за больших накладных расходов на копирование данных. В следующем подразделе будут показаны два более интересных примера.
ImageMagick и OpenCL
Поддержка OpenCL появилась в ImageMagick уже достаточно давно, однако по умолчанию она не активирована ни в одном дистрибутиве. Поэтому нам придется собрать IM самостоятельно из исходников. Ничего сложного в этом нет, все необходимое уже есть в SDK, поэтому сборка не потребует установки каких-то дополнительных библиотек от nVidia или AMD. Итак, скачиваем/распаковываем архив с исходниками:
Далее устанавливаем инструменты сборки:
$ sudo apt-get install build-essential
Запускаем конфигуратор и грепаем его вывод на предмет поддержки OpenCL:
$ LDFLAGS=-L$LD_LIBRARY_PATH ./confi gure | grep -e cl.h -e OpenCL
Правильный результат работы команды должен выглядеть примерно так:
checking CL/cl.h usability. yes
checking CL/cl.h presence. yes
checking for CL/cl.h. yes
checking OpenCL/cl.h usability. no
checking OpenCL/cl.h presence. no
checking for OpenCL/cl.h. no
checking for OpenCL library. -lOpenCL
Словом "yes" должны быть отмечены либо первые три строки, либо вторые (или оба варианта сразу). Если это не так, значит, скорее всего, была неправильно инициализирована переменная C_INCLUDE_PATH. Если же словом "no" отмечена последняя строка, значит, дело в переменной LD_LIBRARY_PATH. Если все окей, запускаем процесс сборки/установки:
$ sudo make install clean
Проверяем, что ImageMagick действительно был скомпилирован с поддержкой OpenCL:
$ /usr/local/bin/convert -version | grep Features
Features: OpenMP OpenCL
Теперь измерим полученный выигрыш в скорости. Разработчики ImageMagick рекомендуют использовать для этого фильтр convolve:
$ time /usr/bin/convert image.jpg -convolve '-1, -1, -1, -1, 9, -1, -1, -1, -1' image2.jpg
$ time /usr/local/bin/convert image.jpg -convolve '-1, -1, -1, -1, 9, -1, -1, -1, -1' image2.jpg
Некоторые другие операции, такие как ресайз, теперь тоже должны работать значительно быстрее, однако надеяться на то, что ImageMagick начнет обрабатывать графику с бешеной скоростью, не стоит. Пока еще очень малая часть пакета оптимизирована с помощью OpenCL.
Выводы
Несмотря на множество самых разных ограничений и сложность разработки софта, GPGPU — будущее высокопроизводительных настольных компов. Но самое главное — использовать возможности этой технологии можно прямо сейчас, и это касается не только Windows-машин, но и Linux.
Согласно Дарвинской теории эволюции, первая человекообразная обезьяна (если
быть точным – homo antecessor, человек-предшественник) превратилась впоследствии
в нас. Многотонные вычислительные центры с тысячью и больше радиоламп,
занимающие целые комнаты, сменились полукилограммовыми ноутами, которые, кстати,
не уступят в производительности первым. Допотопные печатные машинки превратились
в печатающие что угодно и на чем угодно (даже на теле человека)
многофункциональные устройства. Процессорные гиганты вдруг вздумали замуровать
графическое ядро в «камень». А видеокарты стали не только показывать картинку с
приемлемым FPS и качеством графики, но и производить всевозможные вычисления. Да
еще как производить! О технологии многопоточных вычислений средствами GPU,
NVIDIA CUDA и пойдет речь.
GPU — наше все?
Несмотря на все преимущества, техника GPGPU имеет несколько проблем. Первая из них заключается в очень узкой сфере применения. GPU шагнули далеко вперед центрального процессора в плане наращивания вычислительной мощности и общего количества ядер (видеокарты несут на себе вычислительный блок, состоящий из более чем сотни ядер), однако такая высокая плотность достигается за счет максимального упрощения дизайна самого чипа.
В сущности основная задача GPU сводится к математическим расчетам с помощью простых алгоритмов, получающих на вход не очень большие объемы предсказуемых данных. По этой причине ядра GPU имеют очень простой дизайн, мизерные объемы кэша и скромный набор инструкций, что в конечном счете и выливается в дешевизну их производства и возможность очень плотного размещения на чипе. GPU похожи на китайскую фабрику с тысячами рабочих. Какие-то простые вещи они делают достаточно хорошо (а главное — быстро и дешево), но если доверить им сборку самолета, то в результате получится максимум дельтаплан. Поэтому первое ограничение GPU — это ориентированность на быстрые математические расчеты, что ограничивает сферу применения графических процессоров помощью в работе мультимедийных приложений, а также любых программ, занимающихся сложной обработкой данных (например, архиваторов или систем шифрования, а также софтин, занимающихся флуоресцентной микроскопией, молекулярной динамикой, электростатикой и другими, малоинтересными для линуксоидов вещами).
И третье: графические процессоры работают с памятью, установленной на самой видеокарте, так что при каждом задействовании GPU будет происходить две дополнительных операции копирования: входные данные из оперативной памяти самого приложения и выходные данные из GRAM обратно в память приложения. Нетрудно догадаться, что это может свести на нет весь выигрыш во времени работы приложения (как и происходит в случае с инструментом FlacCL, который мы рассмотрим позже).
Но и это еще не все. Несмотря на существование общепризнанного стандарта в лице OpenCL, многие программисты до сих пор предпочитают использовать привязанные к производителю реализации техники GPGPU. Особенно популярной оказалась CUDA, которая хоть и дает более гибкий интерфейс программирования (кстати, OpenCL в драйверах nVidia реализован поверх CUDA), но намертво привязывает приложение к видеокартам одного производителя.
Предисловие
В этой статье мы попытаемся разобраться, зачем нужна технология GPGPU (General-purpose graphics processing units, Графический процессор общего назначения) и все связанные с ней реализации от конкретных производителей. Узнаем, почему эта технология имеет очень узкую сферу применения, в которую подавляющее большинство софта не попадает в принципе, и конечно же, попытаемся извлечь из всего этого выгоду в виде существенных приростов производительности в таких задачах, как шифрование, подбор паролей, работа с мультимедиа и архивирование.
Умножение матриц
Первый «настоящий» пример, который мы рассмотрим, - умножение матриц. Для реализации мы возьмем простой кубический алгоритм умножения матриц, а не алгоритм Штрассена, имеющий время выполнения, близкое к кубическому ~O(n 2.807 ). Для двух матриц: матрицы A размером m x w и матрицы B размером w x n, следующая программа выполнит их умножение и вернет результат - матрицу C размером m x n:
Распараллелить эту реализацию можно несколькими способами, и при желании распараллелить этот код для выполнения на обычном процессоре правильным выбором был бы прием распараллеливания внешнего цикла. Однако графический процессор имеет достаточно большое количество ядер и распараллелив только внешний цикл, мы не сможем создать достаточное количество заданий, чтобы загрузить работой все ядра. Поэтому имеет смысл распараллелить два внешних цикла, оставив внутренний цикл нетронутым:
Эта реализация все еще близко напоминает последовательную реализацию умножения матриц и пример сложения векторов, приводившиеся выше, за исключением индекса, который теперь является двумерным и доступен во внутреннем цикле с применением оператора []. Насколько эта версия быстрее последовательной альтернативы, выполняемой на обычном процессоре? Умножение двух матриц (целых чисел) размером 1024 х 1024 последовательная версия на обычном процессоре выполняет в среднем 7350 миллисекунд, тогда как версия для графического процессора - держитесь крепче - 50 миллисекунд, в 147 раз быстрее!
Что есть сейчас?
В силу своей молодости, а также благодаря описанным выше проблемам, GPGPU так и не стала по-настоящему распространенной технологией, однако полезный софт, использующий ее возможности, существует (хоть и в мизерном количестве). Одними из первых появились крэкеры различных хэшей, алгоритмы работы которых очень легко распараллелить. Также родились мультимедийные приложения, например, кодировщик FlacCL, позволяющий перекодировать звуковую дорожку в формат FLAC. Поддержкой GPGPU обзавелись и некоторые уже существовавшие ранее приложения, самым заметным из которых стал ImageMagick, который теперь умеет перекладывать часть своей работы на графический процессор с помощью OpenCL. Также есть проекты по переводу на CUDA/OpenCL (не любят юниксоиды ATi) архиваторов данных и других систем сжатия информации. Наиболее интересные из этих проектов мы рассмотрим в следующих разделах статьи, а пока попробуем разобраться с тем, что нам нужно для того, чтобы все это завелось и стабильно работало.
GPU уже давно обогнали x86-процессоры в производительности
Во-вторых, в систему должны быть установлены последние проприетарные драйвера для видеокарты, они обеспечат поддержку как родных для карточки технологий GPGPU, так и открытого OpenCL.
И в-третьих, так как пока дистрибутивостроители еще не начали распространять пакеты приложений с поддержкой GPGPU, нам придется собирать приложения самостоятельно, а для этого нужны официальные SDK от производителей: CUDA Toolkit или ATI Stream SDK. Они содержат в себе необходимые для сборки приложений заголовочные файлы и библиотеки.
Работаем!
Для полноценной работы с CUDA нужно:
1. Знать строение шейдерных ядер GPU, так как суть программирования
заключается в равномерном распределении нагрузки между ними.
2. Уметь программировать в среде C, с учетом некоторых аспектов.
Разработчики NVIDIA раскрыли «внутренности» видеокарты несколько
иначе, чем мы привыкли видеть. Так что волей-неволей придется изучать все
тонкости архитектуры. Разберем строение «камня» G80 легендарной GeForce 8800
GTX.
Шейдерное ядро состоит из восьми TPC (Texture Processor Cluster) – кластеров
текстурных процессоров (так, у GeForce GTX 280 – 15 ядер, у 8800 GTS
их шесть, у 8600 – четыре и т.д.). Те, в свою очередь, состоят из двух
потоковых мультипроцессоров (streaming multiprocessor – далее SM). SM (их всего
16) состоит из front end (решает задачи чтения и декодирования инструкций) и
back end (конечный вывод инструкций) конвейеров, а также восьми scalar SP (shader
processor) и двумя SFU (суперфункциональные блоки). За каждый такт (единицу
времени) front end выбирает варп и обрабатывает его. Чтобы все потоки варпа
(напомню, их 32 штуки) обработались, требуется 32/8 = 4 такта в конце конвейера.
Каждый мультипроцессор обладает так называемой общей памятью (shared memory).
Ее размер составляет 16 килобайт и предоставляет программисту полную свободу
действий. Распределяй как хочешь :). Shared memory обеспечивает связь потоков в
одном блоке и не предназначена для работы с пиксельными шейдерами.
Также SM могут обращаться к GDDR. Для этого им «пришили» по 8 килобайт
кэш-памяти, хранящих все самое главное для работы (например, вычислительные
константы).
Мультипроцессор имеет 8192 регистра. Число активных блоков не может быть
больше восьми, а число варпов – не больше 768/32 = 24. Из этого видно, что G80
может обработать максимум 32*16*24 = 12288 потоков за единицу времени. Нельзя не
учитывать эти цифры при оптимизации программы в дальнейшем (на одной чашу весов
– размер блока, на другой – количество потоков). Баланс параметров может сыграть
важную роль в дальнейшем, поэтому NVIDIA рекомендует использовать блоки
со 128 или 256 потоками. Блок из 512 потоков неэффективен, так как обладает
повышенными задержками. Учитывая все тонкости строения GPU видеокарты плюс
неплохие навыки в программировании, можно создать весьма производительное
средство для параллельных вычислений. Кстати, о программировании.
Программирование
Для «творчества» вместе с CUDA требуется видеокарта GeForce не ниже
восьмой серии. С
официального сайта нужно скачать три программных пакета: драйвер с
поддержкой CUDA (для каждой ОС – свой), непосредственно пакет CUDA SDK (вторая
бета-версия) и дополнительные библиотеки (CUDA toolkit). Технология поддерживает
операционные системы Windows (XP и Vista), Linux и Mac OS X. Для изучения я
выбрал Vista Ultimate Edition x64 (забегая вперед, скажу, что система вела себя
просто превосходно). В момент написания этих строк актуальным для работы был
драйвер ForceWare 177.35. В качестве набора инструментов использовался
программный пакет Borland C++ 6 Builder (хотя подойдет любая среда, работающая с
языком C).
Человеку, знающему язык, будет легко освоиться в новой среде. Требуется лишь
запомнить основные параметры. Ключевое слово _global_ (ставится перед функцией)
показывает, что функция относится к kernel (ядру). Ее будет вызывать центральный
процессор, а вся работа произойдет на GPU. Вызов _global_ требует более
конкретных деталей, а именно размер сетки, размер блока и какое ядро будет
применено. Например, строчка _global_ void saxpy_parallel>>, где X –
размер сетки, а Y – размер блока, задает эти параметры.
Символ _device_ означает, что функцию вызовет графическое ядро, оно же
выполнит все инструкции. Эта функция располагается в памяти мультипроцессора,
следовательно, получить ее адрес невозможно. Префикс _host_ означает, что вызов
и обработка пройдут только при участии CPU. Надо учитывать, что _global_ и
_device_ не могут вызывать друг друга и не могут вызывать самих себя.
Также язык для CUDA имеет ряд функций для работы с видеопамятью: cudafree
(освобождение памяти между GDDR и RAM), cudamemcpy и cudamemcpy2D (копирование
памяти между GDDR и RAM) и cudamalloc (выделение памяти).
Все программные коды проходят компиляцию со стороны CUDA API. Сначала берется
код, предназначенный исключительно для центрального процессора, и подвергается
стандартной компиляции, а другой код, предназначенный для графического адаптера,
переписывается в промежуточный язык PTX (сильно напоминает ассемблер) для
выявления возможных ошибок. После всех этих «плясок» происходит окончательный
перевод (трансляция) команд в понятный для GPU/CPU язык.
Читайте также: