Как документировать и обучать других «оптимизированному до неузнаваемости» вычислительно интенсивному коду?

11

Иногда есть 1% кода, который является достаточно интенсивным в вычислительном отношении и требует самого тяжелого вида низкоуровневой оптимизации. Примерами являются обработка видео, обработка изображений и все виды обработки сигналов в целом.

Цель состоит в том, чтобы документировать и обучать методам оптимизации, чтобы код не становился неуправляемым и не склонным к удалению новыми разработчиками. (*)

(*) Несмотря на возможность того, что конкретная оптимизация совершенно бесполезна в некоторых непредвиденных будущих процессорах, так что код все равно будет удален.

Учитывая, что программные предложения (коммерческие или с открытым исходным кодом) сохраняют свои конкурентные преимущества благодаря наличию самого быстрого кода и использованию новейшей архитектуры ЦП, разработчикам программного обеспечения часто приходится настраивать свой код, чтобы он работал быстрее, получая при этом тот же результат для определенного задание, whlist допускает небольшое количество ошибок округления.

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

Связанные с:

rwong
источник
1
Вы можете просто оставить различные версии в коде, закомментированные, с большим количеством комментариев, рассказывающих читателю, что происходит.
Майк Данлавей
1
И не просто рассказывайте им, что делает код, но почему он работает быстрее. Включите ссылки на алгоритмы, если это необходимо, ваши собственные, вики-подобные, документы или ресурсы, доступные в Интернете (в этом случае просто помните о ссылочной гнили, может быть целесообразно скопировать ее в вашу собственную систему документации со ссылкой на оригинал). .)
Марьян Венема
1
@MikeDunlavey: Ой, пожалуйста , не комментируйте это. Просто имейте несколько реализаций одной и той же функции и вызывайте ту, которая является самой быстрой. Таким образом, вы можете легко переключаться на другую версию кода и тестировать их все.
слеске
2
@sleske Иногда просто наличие большего количества двоичного кода может замедлить его.
Quant_Dev
@quant_dev: Да, это может случиться. Я просто думаю, что важно, чтобы код создавался и запускался (в идеале) регулярно, чтобы поддерживать его в актуальном состоянии. Может быть, построить его только в режиме отладки.
слеске

Ответы:

10

Краткий ответ

Сохраняйте оптимизации локальными, делайте их очевидными, хорошо документируйте их и упростите сравнение оптимизированных версий друг с другом и с неоптимизированной версией как с точки зрения исходного кода, так и с точки зрения производительности во время выполнения.

Полный ответ

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

В идеале вам необходимо включить тестирование производительности в процесс сборки, чтобы вы могли узнать, когда новые технологии делают недействительными старые оптимизации.

Помнить:

Первое правило оптимизации программы: не делайте этого.

Второе правило оптимизации программы (только для экспертов!): Пока не делайте этого ».

- Майкл А. Джексон

Для того, чтобы узнать, настало ли сейчас время, требуется бенчмаркинг и тестирование.

Как вы упоминаете, самая большая проблема с высокооптимизированным кодом заключается в том, что его трудно поддерживать, поэтому, насколько это возможно, вы должны держать оптимизированные части отдельно от неоптимизированных частей. Делаете ли вы это через компиляцию во время компиляции, вызовы виртуальных функций во время выполнения или что-то среднее между ними, не должно иметь значения. Важно иметь в виду, что когда вы запускаете свои тесты, вы хотите иметь возможность протестировать все версии, которые вас интересуют.

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

пример

Например, допустим, у вас есть функция быстрого преобразования Фурье . Может быть, у вас есть базовая алгоритмическая реализация fft.cи тесты в fft_tests.c.

Затем приходит Pentium, и вы решаете реализовать версию fft_mmx.cс фиксированной запятой, используя инструкции MMX . Позже Пентиум 3 приходит и вы решили добавить версию , которая использует Streaming SIMD Extensions в fft_sse.c.

Теперь вы хотите добавить CUDA , поэтому вы добавляете fft_cuda.c, но обнаруживаете, что с набором тестовых данных, который вы использовали годами, версия CUDA медленнее, чем версия SSE! Вы делаете некоторый анализ и в итоге добавляете набор данных, который в 100 раз больше, и вы получаете ускорение, которое вы ожидаете, но теперь вы знаете, что время установки для использования версии CUDA является значительным и что с небольшими наборами данных вы должны использовать Алгоритм без этой стоимости установки.

В каждом из этих случаев вы реализуете один и тот же алгоритм, все должны вести себя одинаково, но будут работать с разной эффективностью и скоростью на разных архитектурах (если они вообще будут работать). Однако с точки зрения кода вы можете сравнить любую пару исходных файлов, чтобы выяснить, почему один и тот же интерфейс реализован по-разному, и, как правило, самый простой способ - вернуться к исходной неоптимизированной версии.

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

Важно сохранять одинаковые вещи одинаковыми , чтобы различия были очевидны .

Марк Бут
источник
7

В частности, поскольку вы взяли пример обработки видео и изображений, можно сохранить код как часть той же версии, но активным или неактивным в зависимости от контекста.

Пока вы не упомянули, я предполагаю Cздесь.

Самый простой способ в Cкоде - оптимизировать (а также применять при попытке сделать вещи переносимыми) - это сохранить

 
#ifdef OPTIMIZATION_XYZ_ENABLE 
   // your optimzied code here... 
#else  
   // your basic code here...

Когда вы включаете #define OPTIMIZATION_XYZ_ENABLEво время компиляции в Makefile, все работает соответственно.

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

основной код всегда выполняется через указатель на функцию, например


   codec->computed_idct(blocks); 

Но указатели функций определяются в зависимости от типа примера (например, здесь функция idct оптимизирована для другой архитектуры ЦП.



if(OPTIMIZE_X86) {
  codec->computed_idct = compute_idct_x86; 
}
else if(OPTIMZE_ARM) {
  codec->computed_idct = compute_idct_ARM;
}
else {
  codec->computed_idct = compute_idct_C; 
}

вы должны увидеть код libjpeg и код libmpeg2 и, возможно, использовать ffmpeg для таких методов.

Дипан Мехта
источник
6

Как исследователь я заканчиваю тем, что пишу довольно много кода "узкого места". Однако, как только он будет запущен в производство, ответственность за его интеграцию в продукт и последующую поддержку ложится на разработчиков. Как вы можете себе представить, четкое информирование о том, что и как должна работать программа, имеет первостепенное значение.

Я обнаружил, что есть три основных ингредиента для успешного завершения этого шага

  1. Используемый алгоритм должен быть абсолютно понятным.
  2. Цель каждой линии реализации должна быть ясной.
  3. Отклонения от ожидаемых результатов должны быть выявлены как можно скорее.

Для первого шага я всегда пишу краткий технический документ, который документирует алгоритм. Цель здесь состоит в том, чтобы фактически написать это так, чтобы другой человек мог реализовать это с нуля, используя только технический документ. Если это хорошо известный, опубликованный алгоритм, достаточно дать ссылки и повторить ключевые уравнения. Если это оригинальная работа, вам нужно быть немного более явным. Это скажет вам, что код должен делать .

Фактическая реализация, которая передается в разработку, должна быть задокументирована таким образом, чтобы все тонкости были явными. Если вы получаете блокировки в определенном порядке, чтобы избежать тупика, добавьте комментарий. Если вы выполняете итерации по столбцам, а не по строкам матрицы из-за проблем с согласованием кэша, добавьте комментарий. Если вы делаете что-то даже немного умное, прокомментируйте это. Если вы можете гарантировать, что технический документ и код никогда не будут разделены (через VCS или аналогичную систему), вы можете обратиться к техническому документу. Результатом может быть более 50% комментариев. Все в порядке. Это скажет вам, почему код делает то, что делает.

Наконец, вы должны быть в состоянии гарантировать правильность перед лицом изменений. К счастью, мы удобный инструмент для автоматизированного тестирования и непрерывной интеграции платформ. Они скажут вам, что на самом деле делает код .

Моя самая сердечная рекомендация - не экономить ни на одном из шагов. Они вам понадобятся позже;)

drxzcl
источник
Спасибо за ваш исчерпывающий ответ. Я согласен со всеми вашими пунктами. С точки зрения автоматического тестирования, я считаю, что адекватно охватить числовой диапазон арифметики с фиксированной запятой и кода SIMD сложно, что я дважды сжигал. Предварительные условия, которые были указаны только в комментариях (без кода для подкрепления), не всегда выполнялись.
rwong
Причина, по которой я до сих пор не принял ваш ответ, заключается в том, что мне нужно больше указаний о том, что означает «короткий технический документ», и какие усилия нужно приложить для его производства. В некоторых отраслях это является частью основного направления деятельности, но в других отраслях необходимо учитывать стоимость и использовать доступные по закону ярлыки.
rwong
Прежде всего, я чувствую вашу боль в отношении автоматизированного тестирования, арифметики с плавающей запятой и параллельного кода. Боюсь, что не существует решения, которое подходит для всех случаев. Обычно я работаю с довольно либеральными допусками, но в вашей отрасли это может быть невозможно.
drxzcl
2
На практике технический документ часто выглядит как первый черновик научной статьи, без «пуховых» частей (без содержательного введения, без абстрактных, минимальных выводов / обсуждений и только ссылок, которые необходимы для его понимания). Я рассматриваю написание статьи как отчет и неотъемлемую часть разработки алгоритма и / или выбора алгоритма. Вы решили реализовать этот алгоритм (скажем, спектральное БПФ). Что именно? Почему вы выбрали этот среди других? Каковы его характеристики распараллеливания? Усилие должно быть пропорционально отбору / разработке.
drxzcl
5

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

Комментарии должны включать ссылки на спецификации или аппаратный справочный материал.

При необходимости используйте общеотраслевую терминологию и имена алгоритмов - например, «архитектура X генерирует прерывания ЦП для не выровненных чтений, поэтому это устройство Даффа заполняет до следующей границы выравнивания».

Я бы использовал именование переменных in-your-face, чтобы избежать недопонимания происходящего. Не венгерский, но такие вещи, как «шаг», чтобы описать расстояние в байтах между двумя вертикальными пикселями.

Я также дополнил бы это коротким, удобочитаемым документом, который имеет диаграммы высокого уровня и блочный дизайн.

JBRWilkinson
источник
1
Использование единой терминологии для одной вещи (например, использование «шага» над терминами схожих значений, например «шаг», «выравнивание») в одном и том же проекте поможет. Это несколько сложно при объединении нескольких кодовых баз проекта в один проект.
rwong