Вопрос:
Консенсус индустрии программного обеспечения заключается в том, что чистый и простой код имеет основополагающее значение для долгосрочной жизнеспособности базы кода и организации, которой он принадлежит. Эти свойства приводят к снижению затрат на обслуживание и увеличению вероятности продолжения работы базы кода.
Однако код SIMD отличается от общего кода приложения, и я хотел бы знать, существует ли аналогичный консенсус в отношении чистого и простого кода, применяемого конкретно к коду SIMD.
Предыстория моего вопроса.
Я пишу много SIMD-кода (одной инструкции, нескольких данных) для различных задач обработки и анализа изображений. Недавно мне также пришлось перенести небольшое количество этих функций с одной архитектуры (SSE2) на другую (ARM NEON).
Код написан для сжатого программного обеспечения, поэтому он не может зависеть от проприетарных языков без неограниченных прав на распространение, таких как MATLAB.
Пример типичной структуры кода:
- Использование типа матрицы OpenCV (
Mat
) для управления всей памятью, буфером и временем жизни. - После проверки размера (размеров) входных аргументов используются указатели на начальный адрес каждой строки пикселей.
- Число пикселей и начальные адреса каждой строки пикселей из каждой входной матрицы передаются в некоторые низкоуровневые функции C ++.
- Эти низкоуровневые функции C ++ используют встроенные функции SIMD (для архитектуры Intel и ARM NEON ), загружая и сохраняя необработанные адреса указателей.
- Характеристики этих низкоуровневых функций C ++:
- Исключительно одномерный (последовательный в памяти)
- Не имеет дело с распределением памяти.
(Каждое распределение, включая временные, обрабатывается внешним кодом с использованием средств OpenCV.) - Диапазон длины имен символов (внутренние, имена переменных и т. Д.) Составляет примерно 10–20 символов, что довольно много.
(Читается как техно-болтовня.) - Повторное использование SIMD-переменных не рекомендуется, потому что компиляторы довольно глючат в правильном разборе кода, который не написан в стиле кодирования с «одним назначением».
(Я подал несколько отчетов об ошибках компилятора.)
Какие аспекты программирования SIMD могут привести к тому, что обсуждение будет отличаться от общего случая? Или почему SIMD отличается?
С точки зрения первоначальной стоимости разработки
- Хорошо известно, что первоначальная стоимость разработки SIMD-кода C ++ с хорошей производительностью составляет примерно 10-100 раз (с большим отрывом) по сравнению с небрежно написанным кодом C ++.
- Как отмечено в ответах на Выбор между производительностью и читаемым / более чистым кодом? Большая часть кода (включая случайно написанный код и код SIMD) изначально не является ни чистой, ни быстрой .
- Эволюционные улучшения производительности кода (как в скалярном, так и в SIMD-коде) не приветствуются (потому что это рассматривается как своего рода переработка программного обеспечения ), а стоимость и выгоды не отслеживаются.
С точки зрения склонности
(например, принцип Парето, ака правило 80-20 )
- Даже если обработка изображений составляет всего 20% программной системы (как по размеру кода, так и по функциональности), обработка изображений сравнительно медленная (если смотреть в процентах от затраченного времени ЦП), занимая более 80% времени.
- Это связано с эффектом размера данных: типичный размер изображения измеряется в мегабайтах, тогда как типичный размер данных без изображения измеряется в килобайтах.
- В коде обработки изображений SIMD-программист обучен автоматически распознавать 20% код, содержащий горячие точки, путем идентификации структуры цикла в коде C ++. Таким образом, с точки зрения программиста SIMD, 100% «важного кода» является узким местом производительности.
- Часто в системе обработки изображений существует множество горячих точек, которые занимают сопоставимые пропорции времени. Например, может быть 5 горячих точек, каждая из которых занимает (20%, 18%, 16%, 14%, 12%) общего времени. Для достижения высокой производительности все горячие точки должны быть переписаны в SIMD.
- Это суммируется как правило всплывающих подсказок : шарик нельзя вытолкнуть дважды.
- Предположим, есть несколько воздушных шаров, скажем, 5 из них. Единственный способ уничтожить их - это выдвинуть их один за другим.
- После того, как первый шарик вытолкнут, оставшиеся 4 шарика теперь составляют более высокий процент от общего времени выполнения.
- Чтобы сделать дальнейший выигрыш, нужно затем выскочить другой шар.
(Это противоречит правилу оптимизации 80-20: хороший экономический результат может быть достигнут после сбора 20% плодов с минимальными висячими.)
С точки зрения читабельности и обслуживания
SIMD-код явно трудно читать.
- Это верно даже в том случае, если следовать всем передовым методам разработки программного обеспечения, таким как именование, инкапсуляция, правильность констант (и выявление побочных эффектов), декомпозиция функций и т. Д.
- Это верно даже для опытных программистов SIMD.
Оптимальный код SIMD сильно искажен (см. Примечание) по сравнению с его эквивалентным кодом прототипа C ++.
- Существует много способов искажать код SIMD, но только 1 из 10 таких попыток даст приемлемо быстрые результаты.
- (То есть в настройках 4x-10x прироста производительности, чтобы оправдать высокую стоимость разработки. На практике наблюдался еще более высокий прирост).
(Примечание)
Это основной тезис проекта MIT Halide - цитирование заголовка статьи дословно:
«Отсоединение алгоритмов от расписаний для легкой оптимизации конвейеров обработки изображений»
С точки зрения прямой применимости
- Код SIMD строго привязан к единой архитектуре. Каждая новая архитектура (или каждое расширение регистров SIMD) требует переписывания.
- В отличие от большинства программных разработок, каждый кусок кода SIMD обычно пишется с единственной целью, которая никогда не меняется.
(За исключением портирования на другие архитектуры.) - Некоторые архитектуры поддерживают идеальную обратную совместимость (Intel); некоторые отстают на тривиальную величину (ARM AArch64, заменяя
vtbl
наvtblq
), но этого достаточно, чтобы заставить некоторый код не скомпилироваться.
С точки зрения навыков и обучения
- Не ясно, какие предпосылки знаний необходимы, чтобы правильно обучить нового программиста писать и поддерживать код SIMD.
- Выпускники колледжей, которые изучили программирование SIMD в школе, кажется, презирают и отклоняют это как непрактичный путь карьеры.
- Чтение разборки и низкоуровневое профилирование производительности считаются двумя основными навыками написания высокопроизводительного кода SIMD. Однако неясно, как систематически обучать программистов этим двум навыкам.
- Современная архитектура процессора (которая значительно отличается от того, чему учат в учебниках) делает обучение еще более трудным.
С точки зрения правильности и связанных с дефектами затрат
- Одна функция обработки SIMD на самом деле достаточно связна, чтобы можно было установить правильность с помощью:
- Применяя формальные методы (с ручкой и бумагой) , и
- Проверка целочисленных выходных диапазонов (с кодом прототипа и выполнением вне времени выполнения) .
- Однако процесс проверки является очень дорогостоящим (тратит 100% времени на проверку кода и 100% времени на проверку прототипа), что в три раза увеличивает и без того дорогостоящую стоимость разработки кода SIMD.
- Если какой-либо ошибке удается ускользнуть через этот процесс проверки, почти невозможно «починить» (исправить), кроме как заменить (перезаписать) предполагаемую неисправную функцию.
- SIMD-код страдает от тупых дефектов в компиляторе C ++ (оптимизирующий генератор кода).
- SIMD-код, сгенерированный с использованием шаблонов выражений C ++, также сильно страдает от недостатков компилятора.
С точки зрения разрушительных инноваций
Многие решения были предложены в научных кругах, но лишь немногие видят широкое коммерческое использование.
- MIT Halide
- Стэнфордская темная комната
- NT2 (Numeric Template Toolbox) и связанный с ним Boost.SIMD
Библиотеки с широко распространенным коммерческим использованием, по-видимому, не поддерживают SIMD.
- Библиотеки с открытым исходным кодом кажутся теплее SIMD.
- Недавно у меня появилось это наблюдение из первых рук после профилирования большого количества функций API OpenCV, начиная с версии 2.4.9.
- Многие другие библиотеки обработки изображений, которые я профилировал, также не интенсивно используют SIMD, или они пропускают истинные горячие точки.
- Коммерческие библиотеки, похоже, вообще избегают SIMD.
- В некоторых случаях я даже видел библиотеки обработки изображений, которые возвращали оптимизированный для SIMD код в более ранней версии к не-SIMD-коду в более поздней версии, что приводило к серьезному снижению производительности.
(Ответ производителя заключается в том, что необходимо было избегать ошибок компилятора.)
- В некоторых случаях я даже видел библиотеки обработки изображений, которые возвращали оптимизированный для SIMD код в более ранней версии к не-SIMD-коду в более поздней версии, что приводило к серьезному снижению производительности.
- Библиотеки с открытым исходным кодом кажутся теплее SIMD.
Вопрос этого программиста: должен ли код с низкой задержкой иногда быть "безобразным"? связан, и я ранее написал ответ на этот вопрос, чтобы объяснить мою точку зрения несколько лет назад.
Однако этот ответ в значительной степени «умиротворяет» точку зрения «преждевременной оптимизации», то есть точку зрения, которая:
- Все оптимизации преждевременны по определению (или кратковременны по своей природе ), и
- Единственная оптимизация, которая имеет долгосрочное преимущество - это простота.
Но такие точки зрения оспариваются в этой статье ACM .
Все это заставляет меня задаться
вопросом : код SIMD отличается от общего кода приложения, и я хотел бы знать, существует ли аналогичный консенсус в отрасли относительно ценности чистого и простого кода для кода SIMD.
Ответы:
Я написал не много SIMD-кода для себя, но много ассемблерного кода несколько десятилетий назад. AFAIK с использованием встроенных функций SIMD - это, по сути, программирование на ассемблере, и весь ваш вопрос можно перефразировать, просто заменив «SIMD» словом «сборка». Например, пункты, которые вы уже упоминали, как
разработка кода занимает от 10 до 100 раз, чем «код высокого уровня»
это связано с конкретной архитектурой
код никогда не бывает «чистым» и не может быть легко переработан
вам нужны специалисты для написания и поддержания его
отладка и сопровождение трудны, развиваются очень тяжело
ни в коем случае не являются «особенными» для SIMD - эти пункты верны для любого языка ассемблера, и все они являются «отраслевым консенсусом». И вывод в индустрии программного обеспечения также почти такой же, как и для ассемблера:
не пишите, если вам не нужно - используйте язык высокого уровня везде, где это возможно, и пусть компиляторы выполняют тяжелую работу
если компиляторов недостаточно, хотя бы инкапсулируйте части «низкого уровня» в некоторых библиотеках, но избегайте распространения кода по всей вашей программе
поскольку практически невозможно написать «самодокументирующийся» ассемблер или код SIMD, попробуйте сбалансировать это с помощью большого количества документации.
Конечно, с «классической» сборкой или машинным кодом действительно есть разница: сегодня современные компиляторы обычно производят высококачественный машинный код на языке высокого уровня, который часто лучше оптимизируется, чем код на ассемблере, написанный вручную. Для популярных сегодня SIMD-архитектур качество доступных компиляторов намного ниже, чем у AFAIK, и, возможно, оно никогда не достигнет этого, поскольку автоматическая векторизация все еще является темой научных исследований. См., Например, эту статью, в которой описываются различия в оптимизации между компилятором и человеком, давая представление о том, что создать хорошие SIMD-компиляторы может быть очень сложно.
Как вы уже описали в своем вопросе, существуют также проблемы с качеством современных библиотек. Поэтому лучше всего надеяться, что в ближайшие годы качество компиляторов и библиотек повысится, возможно, оборудование SIMD придется изменить, чтобы стать более «дружественным к компилятору», возможно, специализированные языки программирования, поддерживающие более простую векторизацию (например, Halide, который Вы упомянули дважды) станет более популярным (разве это не сила Fortran?). Согласно Википедии , SIMD стал «массовым продуктом» около 15–20 лет назад (а Галиду меньше 3 лет, когда я правильно истолковываю документы). Сравните это со временем компиляторов для "классического" языка ассемблера, необходимого, чтобы стать зрелым. Согласно этой статье в Википедиипотребовалось почти 30 лет (с ~ 1970 года до конца 1990-х годов), пока компиляторы не превысили производительность человеческих экспертов (в производстве непараллельного машинного кода). Поэтому нам, возможно, придется подождать еще 10–15 лет, пока то же самое не случится с компиляторами с SIMD.
источник
Моя организация занималась именно этой проблемой. Наши продукты находятся в видеопространстве, но большая часть кода, который мы пишем, - это обработка изображений, которая будет работать и для неподвижных изображений.
Мы «решили» (или, возможно, «справились») проблему, написав собственный компилятор. Это не так безумно, как кажется на первый взгляд. У него ограниченный набор входов. Мы знаем, что весь код работает с изображениями, в основном с изображениями RGBA. Мы установили некоторые ограничения, например, чтобы входной и выходной буферы никогда не перекрывались, поэтому нет наложения указателей. Такие вещи.
Затем мы пишем наш код на языке затенения OpenGL (glsl). Он компилируется в скалярный код, SSE, SSE2, SSE3, AVX, Neon и, конечно, фактический glsl. Когда нам нужно поддержать новую платформу, мы обновляем компилятор для вывода кода для этой платформы.
Мы также делаем мозаику изображений, чтобы улучшить когерентность кэша, и тому подобное. Но поддерживая обработку изображений в небольшом ядре и используя glsl (который даже не поддерживает указатели), мы значительно уменьшаем сложность компиляции кода.
Этот подход не для всех, и у него есть свои проблемы (например, вам нужно убедиться в корректности компилятора). Но это сработало довольно хорошо для нас.
источник
Похоже, это не добавит слишком много накладных расходов на обслуживание, если вы решите использовать язык более высокого уровня:
против
Конечно, вам придется столкнуться с ограничениями библиотеки, но вы не будете поддерживать ее самостоятельно. Может быть хороший баланс между затратами на техническое обслуживание и выигрышем в производительности.
http://blogs.msdn.com/b/dotnet/archive/2014/04/07/the-jit-finally-proposed-jit-and-simd-are-getting-married.aspx
http://blogs.msdn.com/b/dotnet/archive/2014/05/13/update-to-simd-support.aspx
источник
В прошлом я занимался программированием на ассемблере, а не программированием SIMD.
Вы рассматривали возможность использования компилятора с поддержкой SIMD, такого как Intel? Интересно ли руководство по векторизации с помощью компиляторов Intel® C ++ ?
В некоторых ваших комментариях, таких как «всплывающие подсказки», предлагается использовать компилятор (чтобы получить преимущества, если у вас нет ни одной горячей точки).
источник