Pull to refresh

Comments 55

Я конечно подозревал о сложностях работы с GPGPU но что все на столько плохо, для меня, конечно, неожиданность.
Оно не то чтобы совсем плохо. Просто оно очень сильно отличается от CPU и, соответственно, надо привыкнуть к особенностям архитектуры GPU. То, что на CPU будет казаться жутко медленным и неоптимальным, на GPU может быть как раз лучшим решением, а мы к этому не привыкли. И наоборот.

Я в следующем посте напишу про то как традиционная оптимизация GPU кода может вредить производительности. Слишком уж много раз я на этом попадался… сидишь оптимизируешь, вот видно прямо, что меньше операций должно выполняться, а получается медленнее.
Хмм, а подскажите, пожалуйста, есть ли какие-нибудь кулинарные книжки по разработке под GPU с нуля?
«С нуля» означает, что не для новичка, а с нуля программы, когда она еще не испорчена обычными шаблонами, заточенными под CPU. Т.е. некий набор правил, которые следует пошагово держать в голове все время разработки.

И еще: оцените, пожалуйста, адекватность использования (в т.ч. для запуск игрового веб-сервера, например) на базе супер-компьютеров Nvidia, при условии, что ПО будет написано именно с заточкой под GPU.
Что касается книжек по GPGPU, то с ними все печально, есть пара популярных книжек от нвидии. Но, честно говоря, сильно много пользы они мне не принесли, там больше для совсем начинающих и про сам язык, простейшие вещи. Книжку о том, как надо думать чтобы все получилось я так и не нашел. Поэтому посоветовать ничего не могу кроме многократного перечитывания официальной документации по архитектуре на которой планируете все это запускать.

На счет правил все интересно, тут на целую книгу наберется:)
Из основного:
— Забудьте сразу про то, что оно может запускаться на разных архитектурах. Если выбрали Fermi, то все под него.
— GPU любит много ПРОСТЫХ потоков, если Вы не можете запустить 30к, до теоретического максимума не дойти.
— Сразу думайте о том, как вы будете читать ваши данные и храните соответственно.
— Латентность запуска ядра ~70микросекунд(не мс)
— Иерархические структуры = либо медленно либо убьетесь поддерживать и придумывать как их распараллелить
— На GPU много памяти, не надо ее экономить в ущерб производительности
— На GPU ядра очень простые и мало регистров.
— Главное — думайте заранее о том, как вы храните данные и, что вы с ними планируете делать. Все должно быть последовательно.
— И еще лучше запустить простой алгоритм 3 раза, чем сложный 1 раз.

Я попробую в следующем посте описать подробнее все это дело, сам сейчас думаю над архитектурой геометрической библиотеки под это дело… сильно и долго думаю, но тяжко это идет:)

Игровой веб-сервер… не могу сказать конкретно, так как не представляю, что он должен считать. Если там будет считаться физика, то почему бы и нет, если просто много много расчетов каких-нибудь коэффициентов, то тоже хорошо, а дальше не скажу, у меня просто опыта разработки таких серверов. Все сильно зависит от задачи и от того, сколько вы хотите потратить ресурсов на нее.
>Ограничение седьмое: запуск каждого ядра сопровождается небольшой задержкой
не про заполнение ли конвейера тут идет речь?
Не могу сказать точно, так как не знаю, что там внутри происходит в это время. Возножно и заполнение конвейера. Скорее всего там много чего происходит в это время. Могу сказать, что оно измеряется в десятках микросекунд (50-150) и не является постоянным.
Я думаю, что речь идёт о копировании кернелов на GPU. Для многократного использования одного и того же ядра его лучше хранить в памяти GPU, чем каждый раз делать операцию вызова ядра из хостовой части. Например можно скомпилировать код ядра в CUBIN (только для CUDA) и тогда на время выполнения приложения кернелы не будут многократно копироваться.

Похоже, что мы говорим о разных задержках. Я имел в виду ту, которая происходит при запуске ядра, которое уже скомпилировано и загружено. Т.е. если Вы запустите его 100 раз, Вы потратите 100хВремя задержки, при самом первом запуске задержка еще больше. А если в OpenCL Вы запускаете программу и компилируете ядро в самый самый первый раз, так там уже время измеряется в секундах. Правда потом драйвер уже скомпилированную программу берет из кэша и все происходит быстро. (не считая того, что в нвидевской реализации есть бага:), при изменении файлов, которые инклюдятся в основной файл, драйвер использует старую версию).
Дело в том, что каждый раз, когда вы вызываете кернел из хоста, то происходит копирование, поэтому есть механизм его кеширования. Т.е. проще говоря в стандартном подходе программирования каждый вызов kernel функции равносилен операции копирования кода с хоста на GPU, если не использовать механизмы кеширования, когда данный kernel будет лежать в кеше и не дёргаться многократно. Да задержки есть и там, но они минимальны даже для 100 раз.
Копируется ли кернел или нет, зависит уже от конкретной реализации драйвера. Подозреваю, что какой-то механизм кэширования там есть, но это уже от нас скрыто, да и работает хорошо, поэтому лезть туда желания нет:)
Задержка маленькая… спору нет… пока не возникает желание запустить ядро много много тысяч раз:), как я и говорил обычно в районе 50-150мкс для моих ядер, что для 100 раз уже даст 5-15мс… с одной стороны, не очень много, а с другой не очень то и мало. У меня как раз на днях оказалось, что эта самая задержка кушала достаточное количество времени (ядра были очень очень простыми).
Не совсем верно, по умолчанию у вас нет кеширования kernel, поэтому данным процессом можно управлять (например CUDA API, либо через driver API). Так же, есть возможность скомпилировать cuda код в формате CUBIN, далее загрузить его в GPU и забирать его с GPU, а не с хоста. Можете посмотреть, как это реализовано в Linpack для CUDA (сейчас должен быть доступен для зарегистрированных разработчиков).
Похоже, что тут наши дороги расходятся:) (я про OpenCL и CUDA). OpenCL не имеет доступа к Cuda API и Driver API, что логично в целом, но является виной nVidia так как можно было бы сделать расширения. В результате, я не могу управлять размером кэша Fermi (всегда 16Кб L1) и очень Вам (CUDA программистам) завидую:). То же самое могу сказать про ядра, в OpenCL они компилируются во время выполнения и дальнейшая их судьба не контролируется (во всяком случае я не встречал ничего такого, хотя, честно говоря, не было нужды и не искал особо).
Да согласен, в общем-то я бы не стал винить NVIDIA за это, т.к. OpenCL стандарт и все стараются его придерживаться, а лишние расширения мешают переносимости кода (т.к. специфичны для NVIDIA только), что является основнополагающей причиной OpenCL.
Спасибо, интереснейшая статья! Совсем замечательно было бы, если бы для всех вышеперечисленных фактов были бы небольшие примеры с кодом, где это проявляется.
Спасибо!
Я попробую откладывать кучоки кода со значениями производительности, но не обещаю, что скоро соберется достаточно таких кусочков (в ближайшие месяца 2 пока не начну заниматься своим проектом), которые можно выложить в сети.
Я огорчён… Думал, что будет хоть что-то об OpenCL, а вы здесь даже термины из CUDA берёте… + пора понимать, что у nVidia OpenCL это скопированная CUDA подточеная под OpenCL стандард, а так у них как всё было, так и осталось, может только стандард IEEE 754 научили.
И приводите ограничения не к технологии OpenCL а к самой структуре графических процессоров и работы с графической памятью.

1рвое ограничение это логичное ограничение, всегда 1 kernel спускается на 1 compute unit (субпроцессор в мултипроцессоре), если ваша программа спускается в маленьком количестве волокон, значит надо задуматься.
К 2рому ограничении: графический чип ожидает light weighted кернелы, которые будут быстро обрабатывать только одну маленькую информацию, тй. если вам надо МНОГО информации, используйте локальную память, а только потом лезьте в глобальную, иначе вы работаете со скоростью глобальной памяти.
С 6стым ограничением не согласен. Это можно точно также сказать, что процессор лезет в рам, когда нет чего-то в кеши. Просто это лимит, а с ним надо как-то бороться. Самый логичный способ это и есть записать в рам. Вообщем-то бы было в полне логично, если-бы это можно было предотвратит на уровне компилирования.
Рвать много ифок в кернелы, так это зло…

А также: OpenCL это стандард, платформа, спецификация и API. Ничего об OpenCL здесь не сказанно.

И на собственной шкуре знаю, что не проблема перехрустеть там что-либо, проблема загрузить БЫСТРО информацию по PCI-Express в графическую карту… Я дольше ждал то, когда туда скопируется 256 мб данных, чем их обрабатывать… Даже и не спрашивайте почему 256 мб, а не больше…
Я написал, что работал с GPU основанными на CUDA в самом начале, поэтому все ограничения идут от нее.
Для CPU все будет совсем иначе, но широкого использования CPU+OpenCL я пока не встречал, а из тех тестов, которые попадались, оно очень и очень сильно уступает SSE, так что пока драйвера не смогут делать OpenCL код сравнимый по скорости с SSE, смотреть в сторону OpenCL никто не будет. Cell'ы же штука весьма специфичная. Вот и остается только под GPU писать, хотя больше и не надо:)

Я не совсем понял Ваш комментарий к первому ограничению. То, что надо запускать много волокон — это понятно. Но проблема именно в том, что все вычисления выполняются по 32 волокна (warp). Если будет 1млн волокон, но каждое будет выполнять разные ветки — будет медленно.

Опять же про второе ограничение, оно связано с тем, что данные читаются блоками и получить теоретическую пропускную способность можно только читая последовательно, а это уже накладывает ограничения на типы данных и то, где данные в памяти размещены. Если у вас будет 100мб данных последовательно, то он считается быстро, если же будет те же 100мб данных, но они будут разбросаны в разных участках, все будет читаться медленно.

Про легкие кернелы, так в том то и заключается проблема с GPGPU, что далеко не каждый алгоритм можно так вот сходу представить в виде простых кернелов. Какие-то алгоритмы можно представить, а какие-то потребуют долгой медитации над кодом. А то было бы всем уже давно счастье и считалось бы все на видеокартах:) (Заметка по этому поводу была в том лирическом отступлении, которое я урезал, надо будет его вернуть в следующих частях)

Шестое ограничение есть везде, спору нет, только вот на GPU 63 регистра, а на CPU на несколько порядков больше. Как говорится, почувствуйте разницу. Могу предположить, что среднему программисту для реализации среднего алгоритма нужно количество регистров как раз между CPU и GPU, поэтому на CPU мы не задумываемся по этому поводу, а вот на GPU появляется дополнительная головная боль.

Про много ифок, опять же кому как повезло с алгоритмами, не всегда можно не рвать, а если уж надо, то надо думать как это делать так, чтобы производительности не вредить (см. ограничение первое)

Про PCI-Express и проблему передачи, опять же, у Вас алгоритм/реализация, где все уперлось в эту шину, но это не всегда так. Часто один и тот же алгоритм (часто, но не всегда) можно реализовать по разному, где-то будет упираться в шину, где-то в память, где-то в процессорное время. Вот найти оптимальную реализацию — проблема. Причем варианты могут быть такие, что для CPU никогда бы и не подумал такое делать. Например считать в 4 раза больше данных, чем CPU, но сэкономить при этом на передачи данных и получить прирост производительности.
С большинством согласен, остальное разбирать не хочу, спать пора…

ЗЫ: Вы согласны, что программировать на графические карты намного интереснее, чем для процессоров? :-)
Я всегда рад узнать, что я упустил или недопонял, так что буду рад услышать комментарии.

А про интересно… наверное соглашусь.
Хотя есть подозрение, что получение максимальной производительности на CPU — то еще веселье:). Просто обычно никто не парится, если неоптимальный код работает на сколько-то процентов медленнее, чем мог бы. А вот с GPU разница идет уже в разы и десятки раз, поэтому особого выбора и не остается, только писать оптимально, GPU плохой код не прощает:)
А что мешает делать асинхронное копирование?? Или вам нужен был блок в 256МБ?
Тогда нужен был блок > 1 ГБ :-)
Если данные по разу читаются, может их вообще не надо копировать? Пусть по PCIe потихоньку перетекают к GPU.
Зачем при асинхронном копировании вам блок в 1ГБ?
Ограничение в 512 потоков возникает только если вы пытаетесь использовать все регистры (печально, что больше 63 регистров нельзя). Но если ограничение в другом месте, то может оказаться выгодным использовать меньшее число потоков. Но для того, чтобы эффективно писать на малом числе потоков нужно знать как обойти проблему номер 3.

Если латентность доступа к памяти 800 циклов, то латентность десяти доступов в память равна 810 циклов! Для этого нужно писать не
read1
calculate1
write1
read2
calculate2
write2

а
read1
read2
calculate1
calculate2
write1
write2


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

Таким образом можно, в частности, уменьшать число потоков в двое, увеличивая в двое количество используемых регистров. Какая спрашивается тогда разница?

А дело в том, что иногда у вычислений с индексом 1 и вычислений с индексом 2 есть общие части. При таком способе написания алгоритма они объединяются. В результате количество регистров и вычислений часто увеличивается менее чем в двое. Можно запустить как бы «больше» потоков и получить лучшее скрытие латентности памяти или просто получить оптимизации ускоряющие алгоритм.

Выглядит парадоксально, но тем не менее во многих случаях из-за этого оказывается, что чем меньше потоков, тем лучше. См. статью http://www.cs.berkeley.edu/~volkov/volkov10-GTC.pdf из которой я узнал про этот метод.
Вы совершенно правы, маленькое число потоков — это не всегда плохо. Ограничений бывает много. Все зависит от каждого конкретного алгоритма и его реализации. Только вот понимание всех возможных узких мест — не такая уж тривиальная задача. Вообще, хотелось бы какой-то упрощенный визуальный эмулятор GPU, где можно наглядно посмотреть как куда какие запросы улетают, где кто что ждет… но это из разряда фантастики:)

Только к примеру надо добавить, что если
read1
будет читать из разных участков памяти, то будет латентность 800*32, так как в результате будет куча запросов к памяти и целый warp будет ждать.

Спасибо за ссылку! Вроде не попадалась мне эта презентация, хотя подобным методом пользуюсь. Без экспериментов никуда:)
Насчёт эмулятора идея хорошая и на мой взгляд очевидная. Неужели до сих пор нет?
У меня как бы есть опыт реализации эмуляторов процессоров, может заняться на досуге…
Для CUDA вроде есть, а вот для OpenCL не встречал. Если сделаете что-то юзабельное, да с возможностью отладки, цены Вам не будет:)
Я по возможности делаю свои ядра такими, чтобы на CPU запускались и компилировались студией, но всему есть свои пределы. Вообще, хотелось бы что-то простое, даже не симулятор, а утилитку, которая бы для каждой строки показывала сколько регистров съедено да сколько операций совершено, сколько запросов к памяти отправлено. Даже такая статистика бы пригодилась.
Зачем вам эмулятор графической карты на процессоре у OpenCL? На CPU ведь всё работает «точно также» как и на GPU… Только ограничения другие (group_size (GPU: 256, 256, 256 vs CPU: 1024,1024,16), memory, итд..)… + нет быстрого спущения кернелов…
А для конкретного просмотра как кернелы компилируются, так можно воспользоваться «AMD APP KernelAnalyzer»…
Есть несколько ответов на этот вопрос:
1) У меня нет ничего от AMD, так уж вышло и вряд ли изменится, а архитектуры таки у них разные,
2) Точно также Вы и сами в кавычки поставили,
3) Из тех утилит, которыми я пользовался для nVidia не попалось ничего, о чем можно было бы сказать, что этого достаточно.

Мне хочется видеть какая команда сколько каких ресурсов может потратить. Я просто хочу что-то наглядное. Пусть даже статическое, но под nVidia и удобное. Но это уже так… из серии чего бы хотелось…

Что реально нужно, так это дебагер (для Windows+nVidia+OpenCL)
Интересная статья! Обязательно пишите продолжение! Было бы очень приятно, если бы в дальнейшем были примеры с кодом, а повествование рассматривало и особенности AMD карт.

На мой взгляд, не смотря на всю эту проблематику — не всё так грустно. Конечно, для сложного кода построение программы усложняется на порядки по сравнению с CPU, но для простых операций (применить простой фильтр к изображению, произвести простые матричные расчёты) производительность может разогнать даже не специалист. При этом программа будет работать достаточно быстро и на Nvidia и на AMD.
Как будет время, обязательно продолжу. Рабочий код сейчас показать не могу, но, опять же как будет время, попробую сделать несколько тестовых примерчиков, а то мне самому интересно проверить некоторые вещи. С AMD сложнее… у меня есть кучка карт от nVidia и ни одной от AMD (нвидия как-то добрее, например прислала нам Quadro 6000 бесплатно (при стоимости в несколько килобаксов) для рисерчей). Но если попадется, попробую.

А про сложный и простой код правда. Только вот не так уж много в реальных приложениях этих самых простых алгоритмов, а все сложное уже должно оптимизироваться под каждую архитектуру… тут с до-Fermi на Fermi не перенесешь особо (без потери производительности относительно максимума), а на другую совершенно архитектуру еще хуже.
Это та же проблема и с процессорами. Вроде новые процессоры, быстрее-выше-сильнее… но на деле выявляется куча проблем, которые надо решать либо оптимизацией компилятора, либо методом распараллеливания с учетом потери относительной производительности.
У всего этого есть один большой плюс — требуется постоянная работа на алгоритмами и их оптимизацией, что в конечном итоге может выйти в хороший прирост (как производительности, так и денежный).
Действительно графические процессоры не работают с числами типа double?
В большинстве GPU не поддерживают double, достаточно посмотреть любой современное 3d API, чтобы убедиться в этом ). Хотя ряд архитектур GPU (в частности Fermi) имеет нативную поддержку double'ов.
это хорошо, значит в некоторых задачах можно переходить с MPI, потому что, например, для преобразования Фурье, распределенная система никак не подходит.
Есть только маленькая проблема — на compute capability 2.0 производительность double в 8 раз ниже, а в 2.1 — в 12 раз. В результате распараллеливание + SSE часто дает лучшие результаты, чем видеокарта.
интересно, с чем связана такая медленная работа с double, ведь Fermi выпускается в большей степени для научных расчетов, а в них без двойной точности ну совсем никак.
Потому что иначе карты не будут покупать геймеры. Ведь в 3d-графике нужны расчеты с float.

Если бы ученых было столько же, сколько геймеров, были бы видеокарты которые float считать не умеют. Вычисления с double при этом все равно не достигли бы такой же скорости, поскольку они требуют больше места на чипе, а следовательно вычислительных блоков можно разместить меньше.
конечно странно, та же Fermi стоит в несколько раз дороже обычной видеокарты. Многие вычислительные центры перешли бы со временем к графической платформе. В вычислительном центре РАН, например, такой вопрос был на повестке. При таком раскладе, массового использовании GPU в научно-инженерных расчетах не будет, хотя идея сама по себе хорошая, насколько я понял ее сейчас подхватил интел.
Fermi и есть обычная видеокарта. У меня в ноутбуке стоит мобильная версия Fermi. Может вы путаете ее с Tesla?

В науке не всем обязательны double. Я так вообще чаще всего вообще с целыми числами на видеокарте работаю. Судьба double на GPU пока не ясна. Компании пока лишь пробуют землю выпуская пробные версии, чтобы понять что же нужно людям. Если окажется, что на этом рынке есть большие деньги, то сделают и видеокарты, эффективно работающие с double. Так что пишите статьи о том, что получается хорошо, а что получается плохо и возможно вас услышат.
Стоимость Tesla меня тоже шокирует. Я не могу ее объяснить.
как мне объяснили, Nvidia дает гарантию, что Tesla не будет глючить в расчетах, в отличии от бытовой карточки
при интегрировании диффуров, если много шагов считать нужно, то без double никак, а это очень частая задача. Суммирование рядов, интегралы и тд, тоже требуют double
В 8/12 раз ниже по сравнению с чем?
С single, а не CPU.
100 кратное ускорение становится 10 кратным, которое перекрывается 4 ядерным процессором с SSE.
Что правда, то правда. Только вот получить больше 6 CPU ядер в среднем компьютере весьма и весьма тяжко, а получить 4 GPU очень даже легко, каждая вторая материнка 2 PCIe слота имеет, куда две GTX590 запихнуть можно за сравнимые (с 6 ядерным процом) деньги.
А если дальше посмотреть, все еще веселее… в плане цен становится…

Для сравнения производительности могу сказать, что GTX580 обгоняет одно ядро i7-2600 раз эдак в 30-130 (все зависит от алгоритма, данных итд итп).
Угу. Поэтому компьютеры на видеокартах имеют смысл.
На чистых видеокартах не имеют… кто-то помню извращался на ферми что-то х86 симулировал, но жутко медленно. А вот использовать GPU как сопроцессор, очень даже хорошо.
Не совсем так. Ускорение само по себе не является константным, поэтому говорить о 100 или 10 кратном увеличении имеет смысл лишь тогда, когда код полностью адаптирован на GPU и результаты получены(зачастую это редкие случаи, т.к. алгоритмы не всегда поддаются распараллеливанию). И уж тем более перекрыть 4-х ядерным процессором это нельзя, ибо всплывает куча нюансов, на которых процессор просидает. Вопрос кол-ва double и single производительности — это вопрос баланса цены.
Все несколько интересней на самом деле.
NV compute capability <1.3 (если не ошибаюсь) не умеют работать с double в принципе.
NV compute capability <2.0 Имеют один double блок на мультипроцессорв, в результате у них примерно в 10 раз меньше скорость вычисления double чем single
NV compute capability 2.0 может работать с double в ДВА раза медленнее, чем с single (и это правда для Tesla и Quadro), но производительность GeForce специально ограничена еще в 4 раза, дабы люди покупали Quadro.

Про AMD не знаю.
Да там есть одна проблема — нужно мыслить параллельно, т.е. в буквальном смысле: забудьте чему вас учили до этого! Сам программировал для GPU на OpenCL и могу с уверенностью сказать — там все завязано на низкоуровневую оптимизацию распределения памяти. А что вы хотели? Думали, что х200 получить так просто, как написать Hello World на C#?
Sign up to leave a comment.

Articles