Оптимизация кода

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

Какие стратегии вы считали неуспешно при оптимизации кода?

Переписывать. В какой момент вы решили прекратить оптимизацию и сказать: "Это так же быстро, как и без полной перезаписи". В каких случаях вы в любом случае выступали бы за простую полную переписку? Как бы вы его проектировали?

Ответ 1

Профиль перед попыткой любой оптимизации.

9 раз из 10, время не будет потребляться там, где это возможно.

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

Обязательная цитата Дональда Кнута:

"Мы должны забыть о небольшой эффективности, скажем, около 97% времени: преждевременная оптимизация - корень всех злых"

Ответ 2

Шаги:

  • Measure
  • Анализ
  • Решите
  • Выполнить
  • Повторите

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

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

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

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

Затем вернитесь к началу и измерьте, проанализируйте, решите, выполните и т.д.

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

Ответ 3

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

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

Ответ 4

Прежде всего не забывайте об этом:

  • Преждевременная оптимизация - это корень всех зол
  • Производительность исходит от дизайна

Во-вторых,

Не принимайте try и смотрите

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

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

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

Ответ 5

Помимо профилирования, как все упоминают, два решения, которые я всегда возглавляю для первого (после профилирования), являются Memoization и Lazy loading, их легко реализовать и, как правило, имеют большое значение.

Ответ 6

Все хорошие ответы.

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

Предположим, что для простоты программа выполняет 20 гигабайт для запуска, когда требуется 10. Если я сделаю паузу 10 раз в случайном порядке, то в 5 из этих случаев, более или менее, это будет в одном из этих ненужных циклов. Я вижу, нужен ли цикл, просматривая каждый уровень стека вызовов. Если любая команда вызова на любом уровне стека может быть устранена, тогда цикл не нужен. Если такая команда появляется в нескольких стеках, то ее будет ускорять программу на величину, которая составляет примерно процент от количества образцов стека, которые он включен.

Любая инструкция, которая отображается на нескольких стеках, является подозрительной - чем больше стеков, тем больше подозрений. Теперь call _main, вероятно, не тот, который я могу удалить, но
foo.cpp:96 call std::vector::iterator:++
если он отображается на нескольких стеках, определенно является фокусом внимания.

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

Таким образом, оптимизация состоит в выявлении подозреваемых и поиске способа их замены или устранения.

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

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

Это поможет, если вы сможете выбрать мозг того, кто уже работал над программой.

В противном случае (при условии, что код составляет ~ 10 ^ 6 строк, например, я работал) вы можете получить некоторый ускорение довольно легко, но чтобы выйти за рамки этого, может потребоваться несколько месяцев, чтобы добраться туда, где вам удобно делать значительные изменения.

Ответ 7

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

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

Тогда и только тогда начните анализ кода.

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

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

Ответ 8

Сначала решите, какова цель вашей оптимизации - установите цель для синхронизации определенных операций на данной аппаратной платформе. Измерьте производительность точно (убедитесь, что результаты повторяемы) и в производственной среде (без виртуальных машин и т.д., Если только то, что вы используете на производстве!).

Тогда, если вы решите его уже достаточно быстро, вы можете остановиться там.

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

Ответ 9

Предполагая, что код нуждается в оптимизации, я бы использовал: 1.) Оптимизируйте способ обработки буферов - Оптимизация кеша. Задержки кэша - это одна большая область, которая сосает CPU так же плохо. примерно в пределах 5-10% накладных расходов Поэтому используйте буферы данных в эффективном кэше.

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

3.) Чтение/запись из внешней памяти дороже цикла. максимально сократить доступ к внешней памяти. Или, если у вас есть доступ, сделать это эффективным способом (данные PRaloading, доступ к PArallel DMA и т.д.)

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

-AD

Ответ 10

Язык, который я сделал большую оптимизацию для численного вычисления в C и С++ в Linux. Я обнаружил, что профилирование, в то время как полезно, может исказить результаты вашего рабочего времени, чтобы дешевые, часто называемые операции (например, итератор С++). Так возьмите тех, у кого есть соль. С точки зрения реальных стратегий, которые привели к хорошей скорости, выполните следующие действия:

  • Используйте численные массивы, а не массивы объектов. Например, С++ имеет "сложный" тип данных. Операции над массивом из них были намного медленнее, чем аналогичная операция на двух массивах поплавков. Это может быть обобщено на "использование типов машин" для любого критического кода производительности.
  • Напишите код, позволяющий компилятору быть более эффективным в своих оптимизациях. Например, если у вас есть массив фиксированного размера, используйте индексы массива, чтобы автоматическая векторность (функция компилятора Intel) могла работать.
  • Инструкции SIMD могут обеспечить хорошее ускорение, если ваша проблема вписывается в тип домена, для которого они предназначены (для одновременного умножения/деления поплавков или ints). Это такие вещи, как MMX, SSE, SSE2 и т.д.
  • Использование поисковых таблиц с интерполяцией для дорогостоящих функций, где точные значения не важны. Это не всегда хорошо, так как поиск данных в памяти может стоить дорого.

Надеюсь, это даст вам вдохновение!

Ответ 11

Как уже было сказано многими, профилирование - это ваш первый шаг.

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

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

В основном, однако, есть намного больше возможностей для улучшения ситуации, когда вы начинаете улучшать большие операции. Например, поиск связанного списка - O (n); что означает, что время, затраченное на поиск, увеличивается с той же скоростью, что и размер хранимых в нем данных. Поиск хэш-таблицы - это только O (1), поэтому вы можете добавить больше данных, не увеличивая время поиска (есть другие факторы, когда мы покидаем теоретический мир, так что это не совсем так, но это правда, что большая часть время).

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

Ответ 12

Хорошие стратегии

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

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

Когда останавливать

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