Вопрос:
Консенсус индустрии программного обеспечения заключается в том, что чистый и простой код имеет основополагающее значение для долгосрочной жизнеспособности базы кода и организации, которой он принадлежит. Эти свойства приводят к снижению затрат на обслуживание и увеличению вероятности продолжения работы базы кода.
Однако код 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.