Современные графические процессоры — это мощные звери, способные решать задачи, которые раньше не были их сильной стороной. Приравнивание графических процессоров к высокой производительности может показаться чрезмерным упрощением (а также несправедливым по отношению к ПЛИС). Тем не менее, я по-прежнему советую компаниям, занимающимся сложными вычислениями, исследовать, могут ли графические процессоры иметь применение в их рабочем процессе. Часто речь идет о потенциальном ускорении на порядок или даже больше по сравнению с многопоточными программами на ЦП, не над чем издеваться.
На протяжении всей моей карьеры мне посчастливилось работать с компаниями, которые прекрасно знали о потенциальных преимуществах. Однако во многих случаях мое участие начиналось после неудачной первой попытки. По моему опыту, большинство этих неудач происходит из-за ошибочных ожиданий компаний, не использующих графические процессоры, а также из-за неполных возможностей для первоначальных исследований, ведущих к выбору конструкции, серьезно препятствующему раскрытию всего потенциала графических процессоров. В этой статье я попытался перечислить некоторые из моих любимых раздражающих факторов, которые могут легко привести к разочарованию.
1) Вы недостаточно оптимизировали остальную часть приложения
Большинство компаний, не использующих графические процессоры, хотели бы думать о графических процессорах как о процессорах с большим количеством ядер и более широкими полосами SIMD, но, к сожалению, в этом понимании отсутствуют некоторые важные отличия. Ядра ЦП — это так называемые конструкции, ориентированные на задержку, чтобы быстрее давать результаты, в то время как графические процессоры ориентированы на пропускную способность, что означает, что они предназначены для обеспечения большего количества результатов за то же время. Хотя эти две вещи могут показаться очень похожими, позвольте мне проиллюстрировать разницу на практическом примере с другими нашими любимыми монстрами производительности: автомобилями!
Допустим, у нас есть 100-километровое расстояние между городами А и В. Если мы допустим ограничение скорости 100 км/ч и у нас будет одна полоса движения, это будет означать, что с точки зрения одной машины дорога займет 1 час, так что это наша цифра задержки. Если бы мы уменьшили ограничение скорости до 50 км/ч, легко увидеть, что это удвоило бы нашу задержку. Однако, если бы у нас было четыре полосы вместо одной, то с учетом интенсивного движения, даже несмотря на то, что поездка каждой машины заняла бы в два раза больше времени. Тем не менее из города А в город Б со временем могло добраться в два раза больше автомобилей; следовательно, наша пропускная способность транспортных средств была бы в два раза больше.
Понимание этого примера, вероятно, натолкнет вас на мысль, что любые ожидания сохранения того же общего дизайна программы и ожидания того, что она будет просто волшебно быстрее, немного наивны, поскольку мы говорим не о более быстром оборудовании, а о другом виде.

Возвращаясь к нашему предыдущему примеру, четыре полосы движения между городами А и Б не сильно помогут нашим бедным пассажирам, если дороги города А настолько забиты, что они не могут заполнить все четыре полосы автомобилями. Обычно целью оптимизации является «магистральная» часть, а остальная часть программы, которая может иметь дело с, казалось бы, неинтересными вещами, такими как чтение/запись файлов стандартных форматов и т. д., может легко стать новым узким местом в результате.
Асинхронный, конвейерно-ориентированный дизайн (почти) целых приложений, которые в противном случае считались бы полезными или «продвинутыми оптимизациями» для программ ЦП, является 101 предпосылкой для того, чтобы программное обеспечение имело шанс использовать все более мощные дискретные процессоры. GPU полностью.
2) Вы слишком многого ожидали от библиотек, инструментов и оболочек на базе графического процессора
Первоначально программирование на GPU было тайным искусством немногих счастливчиков, которым это удавалось, первыми из которых были программисты графики, которые могли добиться прекрасных результатов, превратив языки шейдеров, которые были доступны в то время. С появлением платформ GPGPU, таких как CUDA и OpenCL, модель программирования стала более прямой и близкой к аппаратному обеспечению, открывая больше возможностей для оптимизации. С тех пор основное внимание уделяется расширению экосистемы и повышению доступности программирования на GPU. Хотя он имеет много преимуществ с точки зрения демократизации доступа к технологиям, в то же время он также создает ложное впечатление, будто программирование на GPU больше не является жанром боль и выгода.

Например, многие библиотеки имеют некоторый уровень ускорения GPU. Тем не менее, они не всегда имеют согласованный дизайн на уровне API, который мог бы позволить пользователю хотя бы минимизировать транзакции по шине PCI-Express. Даже если бы об этом позаботились, возможности слияния ядра, сокращающие количество обращений к видеопамяти, остаются недосягаемыми. Поскольку, по большому счету, большинство оптимизированных программ для графических процессоров в конечном итоге имеют ограниченную пропускную способность, это само по себе является большим ударом.
Еще более жесткая версия иллюзии доступности — это та, что предлагается через оболочки, обеспечивающие доступ к графическим процессорам из языков, которые (в контексте высокопроизводительных вычислений) в основном предназначены для быстрого прототипирования, а не для рабочего кода. Конечно, доступ к CUDA из существующего кода Python — это фантастический способ ускорить раннюю разработку. Тем не менее, на самом деле, что можно сказать об окончательной производительности из среды, которая так легко становится узким местом из-за глобальной блокировки интерпретатора, если не из болезненной необработанной производительности одного только Python? Тем не менее, многие инженерные группы пытаются сделать именно это, чтобы определить, будет ли целесообразной дальнейшая работа или инициативу следует отбросить.

Снова аналогия с автомобилем: ненужное перемещение промежуточных результатов между этапами вычислений (и, в довершение всего, с помощью Python/OpenCV) недалеко от буксировки Lamborghini с лошадью.
3) Вы сделали только половину работы
Может показаться заманчивым рассматривать оптимизацию графического процессора как естественное продолжение итеративного процесса оптимизации, начавшегося на центральных процессорах, всегда привлекая внимание только к самой горячей точке. Проблема с таким отношением заключается в том, что это может привести к фрагментарной конструкции с неоптимальным количеством ЦП-ГП взад-вперед и точками синхронизации. Действительно, в каждом алгоритме могут быть части, которые сами по себе не имеют особого смысла переносить на ЦП, поскольку они более последовательны по своей природе (помните, что одна быстрая полоса может выиграть гонку!), тем не менее, иногда локально неоптимальное решение может привести к повышению общей производительности всей системы. В идеальных условиях графические процессоры должны поступать из конвейеров и поступать в них асинхронно. Каждое отклонение от этой логики может иметь неожиданные последствия. Я имею в виду, не так уж неожиданно; иначе я бы не поднимал этот вопрос...
Выступаю ли я за перенос как можно большей части основных вычислений на графические процессоры? Да, но не в обычном смысле. Давайте представим, что у вас есть алгоритм сортировки в середине оригинального дизайна, но это также не было проблемой для процессора. Точно такой же алгоритм может не подходить для графического процессора. Должны ли вы повернуть назад? Нет, вы должны просто задавать больше вопросов. В этом конкретном случае вы можете отказаться от быстрой сортировки и вместо этого использовать массово параллельную версию сортировки слиянием после некоторых исследований. Тем не менее, конечный вывод заключается в том, что пока вы сохраняете непредвзятость и не стремитесь портировать все таким же образом, а позволяете себе заново изобретать по кусочкам исходную концепцию, у вас есть шанс получить чистый графический процессор. удобный дизайн, 100% использование даже самых гигантских графических процессоров. Здесь нет автомобильных аналогий; серьезно, просто сделайте всю работу правильно!
После прочтения этой статьи может стать очевидным, что программирование на GPU — это не столько проблема изучения новых инструментов и новых языков, сколько сложность знакомства с аппаратными архитектурами и нашими старыми друзьями: алгоритмами и структурами данных. Это главная причина, по которой мы в TechnoLynx, в отличие от большинства компаний, не разделяем роли исследователя алгоритмов и программиста встраиваемых систем. Вместо этого мы пытаемся набрать/обучить людей, способных уверенно решать обе стороны обычных проблем с ускорением. Если у вас есть какая-либо сложная (здесь, в TechnoLynx, также известная как забавная) проблема с ускорением, которую нужно решить, мы будем более чем рады услышать об этом, а пока, пожалуйста, следите за нами на LinkedIn и Медиум.ком. Мы только начинаем делиться нашими знаниями с сообществом!