Стратегии оптимизации производительности последней инстанции [закрыто]


609

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

Давайте предположим:

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

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

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

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

Ответы:


427

Хорошо, вы определяете проблему там, где, казалось бы, не так много возможностей для улучшения. По моему опыту, это довольно редко. Я попытался объяснить это в статье доктора Доббса в ноябре 1993 года, начав с традиционно хорошо спроектированной нетривиальной программы без очевидных потерь и проводя серию оптимизаций, пока время настенных часов не уменьшилось с 48 секунд. до 1,1 секунды, и размер исходного кода был уменьшен в 4 раза. Мой диагностический инструмент был этим . Последовательность изменений была такой:

  • Первой найденной проблемой было использование кластеров списков (теперь называемых «итераторами» и «классами контейнеров»), занимающих более половины времени. Они были заменены довольно простым кодом, сократив время до 20 секунд.

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

  • Сейчас труднее найти явных виновников, но есть несколько мелких, с которыми я могу что-то сделать, и время сокращается до 13 секунд.

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

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

  • Этот редизайн сделан, сжатие исходного кода в 4 раза, а время сокращено до 10 секунд.

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

  • Больше диагностики показывает, что он проводит время в управлении очередями. В подкладке это сокращает время до 7 секунд.

  • Теперь большое время занимает диагностическая печать, которую я делал. Флеш - 4 секунды.

  • В настоящее время крупнейшими потребителями времени являются звонки на malloc и бесплатные звонки . Перезарядка объектов - 2,6 секунды.

  • Продолжая пробовать, я все еще нахожу операции, которые не являются строго необходимыми - 1,1 секунды.

Общий коэффициент ускорения: 43,6

Сейчас нет двух одинаковых программ, но в не игрушечных программах я всегда видел такой прогресс. Сначала вы получаете легкие вещи, а затем более сложные, пока не дойдете до точки убывающей отдачи. Тогда понимание, которое вы получите, может привести к редизайну, начав новый раунд ускорений, пока вы снова не достигнете убывающей отдачи. Теперь это точка , в которой она может иметь смысл усомниться в том , ++iили i++или for(;;)или while(1)быстрее: виды вопросов , которые я вижу так часто на переполнение стека.

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

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

ДОБАВЛЕНО: jerryjvl запросил несколько примеров. Здесь первая проблема. Он состоит из небольшого количества отдельных строк кода, занимающих более половины времени:

 /* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)

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

Вот вторая проблема, в двух отдельных строках:

 /* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)

Это строит списки, добавляя элементы к своим целям. (Исправление состояло в том, чтобы собрать элементы в массивах и создать списки сразу.) Интересно то, что эти операторы только стоят (то есть находились в стеке вызовов) 3/48 первоначального времени, поэтому их не было На самом деле большая проблема в начале . Тем не менее, после устранения первой проблемы они стоили 3/20 времени, и теперь стали «более крупной рыбой». В общем, так оно и есть.

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

ДОБАВЛЕННАЯ ССЫЛКА: Исходный код, как оригинальный, так и переработанный, можно найти на сайте www.ddj.com за 1993 год, в файле 9311.zip, файлах slug.asc и slug.zip.

РЕДАКТИРОВАТЬ 2011/11/26: В настоящее время существует проект SourceForge, содержащий исходный код в Visual C ++ и подробное описание его настройки. Он проходит только первую половину сценария, описанного выше, и не следует точно такой же последовательности, но все равно получает ускорение на 2-3 порядка.


3
Я хотел бы прочитать некоторые детали шагов, которые вы наметили выше. Можно ли включить некоторые фрагменты оптимизации для аромата? (не делая пост слишком длинным?)
jerryjvl

8
... Я также написал книгу, которая сейчас выходит из печати, поэтому за Amazon она будет стоить смехотворную цену - "Создание лучших приложений" ISBN 0442017405. По сути, тот же материал есть в первой главе.
Майк Данлавей

3
@ Майк Данлавей, я бы посоветовал Google отсканировать его. У них, вероятно, уже есть соглашение с тем, кто купил вашего издателя.
Торбьерн Равн Андерсен

19
@ Thorbjørn: Просто чтобы продолжить, я связался с GoogleBooks, заполнил все формы и отправил им бумажную копию. Я получил электронное письмо с вопросом, действительно ли мне принадлежат авторские права. Издатель Van Nostrand Reinhold, который был куплен International Thompson, который был куплен Reuters, и когда я пытаюсь позвонить или написать по электронной почте, это похоже на черную дыру. Так что это в подвешенном состоянии - у меня еще не было сил, чтобы действительно преследовать его.
Майк Данлавей


188

Предложения:

  • Предварительный расчет, а не повторный расчет : любые циклы или повторные вызовы, которые содержат вычисления, которые имеют относительно ограниченный диапазон входных данных, рассмотрите возможность поиска (массив или словарь), который содержит результат этого вычисления для всех значений в допустимом диапазоне входы. Тогда используйте простой поиск внутри алгоритма.
    Недостатки : если на самом деле используются несколько предварительно вычисленных значений, это может ухудшить ситуацию, также поиск может занять значительную память.
  • Не используйте библиотечные методы : большинство библиотек должны быть написаны для правильной работы в широком диапазоне сценариев, а также для выполнения нулевых проверок параметров и т. Д. Повторная реализация метода может привести к лишению логики, которая не применяется в конкретных обстоятельствах, когда вы его используете.
    Недостатки : написание дополнительного кода означает большую площадь для ошибок.
  • Используйте библиотечные методы : чтобы противоречить самому себе, языковые библиотеки пишутся людьми, которые намного умнее вас или меня; Скорее всего, они сделали это лучше и быстрее. Не реализуйте это самостоятельно, если вы не можете сделать это быстрее (то есть: всегда измеряйте!)
  • Обман : в некоторых случаях, хотя для вашей проблемы может существовать точный расчет, вам может не потребоваться «точное», иногда приближение может быть «достаточно хорошим» и намного быстрее в сделке. Спросите себя, действительно ли имеет значение, если ответ отсутствует на 1%? 5%? даже 10%?
    Недостатки : ну ... ответ не будет точным.

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

37
Обман часто может быть победой. У меня был процесс цветокоррекции, в основе которого лежал 3-вектор, усеянный матрицей 3х3. Процессор имел аппаратное умножение матриц, которое исключало некоторые перекрестные слагаемые и работало очень быстро по сравнению со всеми другими способами, но поддерживало только матрицы 4x4 и 4 вектора с плавающей запятой. Изменение кода для переноса вокруг дополнительного пустого слота и преобразование вычисления в плавающую точку из фиксированной точки позволило получить немного менее точный, но гораздо более быстрый результат.
RBerteig

6
Мошенничество заключалось в использовании умножения матриц, которое исключало некоторые внутренние продукты, что позволяло реализовать в микрокоде одну инструкцию ЦП, которая выполнялась быстрее, чем могла бы даже эквивалентная последовательность отдельных инструкций. Это обман, потому что он не получает «правильный» ответ, просто ответ «достаточно правильный».
RBerteig

6
@RBerteig: просто «достаточно правильно» - это возможность для оптимизации, которую большинство людей упускают из моего опыта.
Мартин Томпсон

5
Вы не можете всегда предполагать, что все умнее вас. В конце концов, мы все профессионалы. Однако вы можете предположить, что используемая вами конкретная библиотека существует и достигла вашей среды из-за ее качества, поэтому написание этой библиотеки должно быть очень тщательным, вы не можете сделать это также только потому, что вы не специализированы в этой области. поле, и вы не вкладываете в него такое же время. Не потому что ты менее умный. Ну же.
v.oddou

164

Когда вы больше не можете улучшить производительность - посмотрите, можно ли вместо этого улучшить воспринимаемую производительность.

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

Несколько примеров:

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

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


27
Индикатор выполнения, ускоряющийся в конце, может восприниматься как более быстрый, чем абсолютно точный. В «Переосмыслении индикатора прогресса» (2007) Харрисон, Аменто, Кузнецов и Белл тестируют несколько типов столбцов на группе пользователей, а также обсуждают некоторые способы перегруппировки операций, чтобы прогресс можно было воспринимать как более быстрый.
Эмиль Викстрем

9
На самом деле, большинство индикаторов прогресса являются фальшивыми, потому что предсказать несколько широко различающихся шагов потока в один процент сложно или иногда невозможно. Просто посмотрите на все эти бары, которые застряли на 99% :-(
Эмиль Викстрем

138

Я провожу большую часть своей жизни именно в этом месте. Широкие штрихи - запустить ваш профилировщик и записать его:

  • Кеш пропускает . Кеш данных является источником номер один в большинстве программ. Повысить частоту обращений к кэшу путем реорганизации поврежденных структур данных, чтобы обеспечить лучшую локальность; упаковать структуры и числовые типы, чтобы исключить потерянные байты (и, следовательно, потерянные выборки из кэша); Предварительная выборка данных везде, где это возможно, чтобы уменьшить задержки.
  • Загрузка-хит-магазины . Предположения компилятора о совмещении указателей и случаи, когда данные перемещаются между несвязанными наборами регистров через память, могут вызывать определенное патологическое поведение, которое приводит к очистке всего конвейера ЦП при загрузке. Найдите места, где поплавки, векторы и целые числа приводятся друг к другу и устраните их. Используйте __restrictобильно, чтобы пообещать компилятору о псевдонимах.
  • Микрокодированные операции . У большинства процессоров есть некоторые операции, которые нельзя конвейеризовать, но вместо этого они запускают крошечную подпрограмму, хранящуюся в ПЗУ. Примерами в PowerPC являются целочисленное умножение, деление и сдвиг по переменной. Проблема в том, что весь конвейер останавливается во время выполнения этой операции. Постарайтесь исключить использование этих операций или, по крайней мере, разбить их на составляющие конвейерные операции, чтобы вы могли воспользоваться преимуществом суперскалярной диспетчеризации во всем, что делает остальная часть вашей программы.
  • Филиал ошибается . Они тоже опустошают трубопровод. Найдите случаи, когда ЦП тратит много времени на заправку канала после ветки, и используйте подсказку о ветке, если таковая имеется, чтобы чаще делать правильный прогноз. Или, что еще лучше, заменяйте ветки условными перемещениями, где это возможно, особенно после операций с плавающей запятой, потому что их канал обычно глубже, и чтение флагов условий после fcmp может привести к остановке.
  • Последовательные операции с плавающей точкой . Сделайте эти SIMD.

И еще одна вещь, которую я люблю делать:

  • Настройте компилятор на вывод списков сборок и посмотрите, что он генерирует для функций горячей точки в вашем коде. Все эти умные оптимизации, которые «хороший компилятор должен сделать для вас автоматически»? Скорее всего, ваш фактический компилятор не делает их. Я видел, как GCC испускает действительно код WTF.

8
Я в основном использую Intel VTune и PIX. Не знаю, смогут ли они адаптироваться к C #, но на самом деле, когда у вас есть этот уровень абстракции JIT, большинство из этих оптимизаций недоступны, за исключением улучшения локальности кэша и, возможно, избежания некоторых ветвей.
Crashworks

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

5
Я думаю, что многие, в том числе и я, были бы заинтересованы в этой "сборке wtf", созданной gcc. Твоя звучит как очень интересная работа :)
BlueRaja - Дэнни Пфлюгофт

1
Examples on the PowerPC ...<- То есть некоторые реализации PowerPC. PowerPC - это ISA, а не процессор.
Билли ONEAL

1
@BillyONeal Даже на современном оборудовании x86 imul может остановить конвейер; см. «Справочное руководство по оптимизации архитектур Intel® 64 и IA-32», §13.3.2.3: «Для выполнения команды целочисленного умножения требуется несколько циклов. Они передаются по конвейеру таким образом, что команда умножения целых чисел и другая команда с большой задержкой могут продвигаться вперед в фаза выполнения. Однако целочисленные команды умножения будут блокировать выдачу других целочисленных команд с одним циклом из-за требования программного порядка ». Вот почему обычно лучше использовать выровненные по размеру массивы и lea.
Crashworks

78

Брось больше оборудования на это!


30
Больше оборудования не всегда возможно, если у вас есть программное обеспечение, которое, как ожидается, будет работать на оборудовании, уже имеющемся в поле.
Даг Т.

76
Не очень полезный ответ для того, кто делает потребительское программное обеспечение: клиент не захочет услышать, как вы говорите: «купите более быстрый компьютер». Особенно, если вы пишете программное обеспечение для чего-то вроде игровой приставки.
Crashworks

19
@ Crashworks, или, в этом отношении, встроенная система. Когда последняя функция наконец-то
включена

71
Однажды мне пришлось отлаживать программу с большой утечкой памяти - размер ее виртуальной машины рос примерно на 1 МБ в час. Коллега пошутил, что все, что мне нужно было сделать, это добавить память с постоянной скоростью . :)
j_random_hacker

9
Больше аппаратных средств: ах да, посредственный путь жизни разработчика. Я не знаю, сколько раз я слышал «добавь еще одну машину и удвои емкость!»
Олоф Форшелл

58

Больше предложений:

  • Избегайте ввода / вывода . Любой ввод / вывод (диск, сеть, порты и т. Д.) Всегда будет выполняться намного медленнее, чем любой код, выполняющий вычисления, поэтому избавьтесь от любого ввода / вывода, который вам строго не нужен.

  • Переместить ввод-вывод вперед : загрузите все данные, которые вам понадобятся для предварительного расчета, чтобы у вас не было повторных ожиданий ввода-вывода в ядре критического алгоритма (и, возможно, в результате повторного поиск диска, при загрузке всех данных одним попаданием можно избежать поиска).

  • Задержка ввода / вывода : не записывайте свои результаты до завершения вычислений, сохраняйте их в структуре данных и затем выводите их за один раз в конце, когда тяжелая работа завершена.

  • Потоковый ввод / вывод : для тех, кто достаточно смел, объедините «I / O заранее» или «Delay I / O» с фактическим расчетом, переместив загрузку в параллельный поток, чтобы во время загрузки большего количества данных вы могли работать вычисляя данные, которые у вас уже есть, или когда вы вычисляете следующую партию данных, вы можете одновременно выписать результаты из последней партии.


3
Обратите внимание, что «перемещение ввода-вывода в параллельный поток» следует выполнять как асинхронный ввод-вывод на многих платформах (например, Windows NT).
Билли ONEAL

2
Ввод-вывод действительно является критической точкой, потому что он медленный и имеет огромные задержки, и вы можете получить этот совет быстрее, но он все еще в корне ошибочен: точки - это задержка (которая должна быть скрыта) и издержки системного вызова ( который должен быть уменьшен путем уменьшения количества вызовов ввода / вывода). Лучший совет: используйте mmap()для ввода, делайте соответствующие madvise()вызовы и используйте aio_write()для записи больших кусков вывода (= несколько МБ).
cmaster - восстановить монику

1
Этот последний вариант довольно легко реализовать в Java, особенно. Это дало ОГРОМНОЕ повышение производительности приложений, которые я написал. Другим важным моментом (больше, чем перемещение ввода-вывода заранее) является его последовательный ввод и вывод с большими блоками. Множество маленьких операций чтения намного дороже, чем 1 большое, из-за времени поиска диска.
BobMcGee

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

48

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

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

Никогда не используйте select *, возвращайте только те поля, которые вам действительно нужны. Это особенно верно, если есть какие-либо объединения, поскольку поля объединения будут повторяться и, следовательно, вызывать ненужную нагрузку как на сервер, так и на сеть.

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

Индекс, индекс, индекс. И обновите эту статистику, если она применима к вашей базе данных.

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

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

Никогда не вставляйте петли любого рода в курок!

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

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

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

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


29

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

Это те эффекты, которыми вы должны управлять. Иногда через микро управление вашим кодом, но иногда через тщательное рассмотрение и рефакторинг.

Многие комментарии уже упоминают код, дружественный к кешу. Есть по крайней мере два разных вида этого:

  • Избегайте задержек извлечения памяти.
  • Более низкое давление на шину памяти (пропускная способность).

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

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

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


25
  • На каком оборудовании вы работаете? Можете ли вы использовать специфичные для платформы оптимизации (например, векторизацию)?
  • Можете ли вы получить лучший компилятор? Например, переключиться с GCC на Intel?
  • Можете ли вы заставить свой алгоритм работать параллельно?
  • Можете ли вы уменьшить количество кешей за счет реорганизации данных?
  • Вы можете отключить утверждения?
  • Микро-оптимизация для вашего компилятора и платформы. В стиле «при условии если / еще, сначала ставьте наиболее распространенное утверждение»

4
Должно быть "переключиться с GCC на LLVM" :)
Zifre

4
Можете ли вы заставить свой алгоритм работать параллельно? - обратное также применимо
Джастин

4
Правда, сокращение количества потоков может быть столь же хорошей оптимизацией
Йохан Котлински

re: микрооптимизация: если вы проверяете вывод asm компилятора, вы часто можете настроить исходный код, чтобы удерживать его вручную для создания лучшего asm. См. Почему этот код C ++ быстрее, чем моя рукописная сборка для проверки гипотезы Коллатца? для получения дополнительной информации о помощи или избиении компилятора на современном x86.
Питер Кордес

17

Хотя мне нравится ответ Майка Данлавей, на самом деле это действительно хороший ответ с подтверждающим примером, я думаю, что его можно выразить очень просто так:

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

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

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

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

HPH, asoudmove.


16

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

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

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


15
  • Встроенные процедуры (исключить вызов / возврат и передачу параметров)
  • Попробуйте исключить тесты / переключатели с помощью поиска таблиц (если они быстрее)
  • Разверните петли (устройство Даффа) до точки, где они просто помещаются в кэш процессора
  • Локализуйте доступ к памяти, чтобы не взорвать ваш кеш
  • Локализуйте связанные вычисления, если оптимизатор этого еще не делает
  • Устранить инварианты цикла, если оптимизатор еще этого не делает

2
Устройство IIRC Даффа очень редко быстрее. Только когда операция очень короткая (как одиночное небольшое математическое выражение)
BCS

12
  • Когда дело доходит до того, что вы используете эффективные алгоритмы, возникает вопрос о том, что вам нужно больше скорости или памяти . Используйте кэширование, чтобы «заплатить» в памяти для большей скорости, или используйте вычисления, чтобы уменьшить объем памяти.
  • Если это возможно (и более эффективно с точки зрения затрат), решите проблему с аппаратным обеспечением - более быстрый процессор, больший объем памяти или жесткий диск могут решить проблему быстрее, чем пытаться ее кодировать.
  • Если возможно, используйте распараллеливание - запускайте часть кода в нескольких потоках.
  • Используйте правильный инструмент для работы . некоторые языки программирования создают более эффективный код, используя управляемый код (например, Java / .NET), ускоряющий разработку, но нативные языки программирования создают более быстрый исполняемый код.
  • Микро оптимизировать . Только в тех случаях, когда это применимо, вы можете использовать оптимизированную сборку для ускорения небольших фрагментов кода, использование SSE / векторной оптимизации в нужных местах может значительно повысить производительность.

12

Разделяй и властвуй

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


9
+1 за звук "мухи" мухобойки, который я услышал, читая последнее предложение.
Брайан Бетчер

11

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

  • ... если это память - найди одну из книг, написанных давным-давно Кнутом, одну из серии "Искусство компьютерного программирования". Скорее всего, речь идет о сортировке и поиске - если у меня не в порядке с памятью, вам придется выяснить, в чем он говорит о том, как бороться с медленным хранением данных на ленте. Мысленно преобразуйте его пару память / лента в вашу пару кэш / основная память (или в пару кэш L1 / L2) соответственно. Изучите все приемы, которые он описывает, - если вы не нашли решения, которое решает вашу проблему, то наймите профессионального компьютерного ученого для проведения профессионального исследования. Если у вас случайно возникла проблема с памятью из-за FFT (при выполнении бабочек из radix-2 в кэше отсутствует обратный бит), не нанимайте ученого - вместо этого вручную оптимизируйте проходы по одному, пока либо победить, либо попасть в тупик. Ты упомянулвыжать до последних нескольких процентов, верно? Если их действительно мало, вы, скорее всего, победите.

  • ... если это процессор - переключитесь на язык ассемблера. Изучите спецификацию процессора - что нужно, тики , VLIW, SIMD. Вызовы функций, скорее всего, являются заменяемыми тик-едоками. Изучите цикл преобразований - конвейер, разверните. Умножения и деления могут быть заменяемыми / интерполированными со сдвигами битов (умножения на маленькие целые числа могут быть заменены сложениями). Попробуйте трюки с более короткими данными - если вам повезет, одна инструкция с 64 битами может оказаться заменяемой двумя на 32 или даже 4 на 16 или 8 на 8 битами. Попробуйте также дольшеданные - например, ваши вычисления с плавающей точкой могут оказаться медленнее, чем двойные вычисления на конкретном процессоре. Если у вас есть тригонометрические вещи, боритесь с ними с заранее рассчитанными таблицами; также имейте в виду, что синусоидальное значение может быть заменено этим значением, если потеря точности находится в допустимых пределах.

  • ... если это сеть - подумайте о сжатии данных, которые вы передаете по ней. Заменить передачу XML двоичным. Протоколы обучения. Попробуйте UDP вместо TCP, если вы можете как-то справиться с потерей данных.

  • ... если это база данных, хорошо, зайдите на любой форум базы данных и попросите совета. Сетка данных в памяти, оптимизация плана запросов и т. Д. И т. Д.

HTH :)


9

Кэширование! Дешевый способ (с точки зрения программиста) сделать почти все быстрее - добавить слой абстракции кэширования в любую область перемещения данных вашей программы. Будь то ввод / вывод или просто передача / создание объектов или структур. Часто к фабричным классам и читателям / писателям легко добавлять кэши.

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


8

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

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

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


8

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

  • Мера : Начните с понимания базовой емкости и топологии сети. Поговорите с соответствующими сетевиками в бизнесе и воспользуйтесь базовыми инструментами, такими как ping и traceroute, чтобы установить (как минимум) задержку сети из каждого клиентского местоположения в течение типичных периодов работы. Затем выполните точные измерения времени определенных функций конечного пользователя, которые отображают проблемные симптомы. Запишите все эти измерения, а также их местоположение, даты и время. Подумайте о том, чтобы встроить функциональность «тестирования производительности сети» для конечного пользователя в ваше клиентское приложение, что позволит вашим опытным пользователям участвовать в процессе улучшения; Подобное расширение их полномочий может оказать огромное психологическое воздействие, когда вы имеете дело с пользователями, разочарованными неэффективной системой.

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

  • Кэш : найдите случаи, когда статические или редко изменяемые данные передаются многократно, и продумайте соответствующую стратегию кэширования. Типичные примеры включают значения «списка выбора» или другие «ссылочные объекты», которые могут быть удивительно большими в некоторых бизнес-приложениях. Во многих случаях пользователи могут согласиться с тем, что им необходимо перезапустить или обновить приложение, чтобы обновить нечасто обновляемые данные, особенно если оно может значительно сократить время отображения часто используемых элементов пользовательского интерфейса. Убедитесь, что вы понимаете реальное поведение уже развернутых элементов кэширования - многие распространенные методы кэширования (например, HTTP ETag) по-прежнему требуют обхода по сети для обеспечения согласованности, а там, где задержка в сети стоит дорого, вы можете избежать ее вообще с другой подход кеширования.

  • Распараллеливание : ищите последовательные транзакции, которые по логике не нужно выполнять строго последовательно, и переделывайте систему для их параллельной выдачи. Я имел дело с одним случаем, когда сквозной запрос имел внутреннюю сетевую задержку ~ 2 с, что не было проблемой для одной транзакции, но когда потребовалось 6 последовательных циклов 2 с, прежде чем пользователь восстановил контроль над клиентским приложением Это стало огромным источником разочарования. Обнаружение того, что эти транзакции на самом деле были независимыми, позволило им выполняться параллельно, уменьшая задержку для конечного пользователя до очень близкой к стоимости одного туда-обратно.

  • Объединение : если последовательные запросы должны выполняться последовательно, ищите возможности объединить их в один более полный запрос. Типичные примеры включают создание новых сущностей, за которыми следуют запросы связать эти сущности с другими существующими сущностями.

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

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

В описанных выше шагах я сосредоточусь на процессе оптимизации, связанном с приложением, но, конечно, вы должны убедиться, что базовая сеть настроена наиболее эффективным образом для поддержки вашего приложения. Привлеките сетевых специалистов к работе и определите, могут ли они применить улучшения емкости, QoS, сжатие сети или другие методы для решения проблемы. Как правило, они не понимают потребностей вашего приложения, поэтому важно, чтобы вы (после этапа анализа) имели возможность обсудить его с ними, а также обосновать любые расходы, которые вы собираетесь просить их понести. , Я сталкивался со случаями, когда неправильная конфигурация сети приводила к тому, что данные приложений передавались по медленной спутниковой линии связи, а не по наземной линии связи, просто потому, что он использовал порт TCP, который не был «хорошо известен» специалистам по сетевым технологиям; Очевидно, что устранение подобной проблемы может оказать существенное влияние на производительность, при этом не требуется никакого программного кода или изменений конфигурации.


7

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


7

Последние несколько% очень сильно зависят от процессора и приложения ....

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

Список можно продолжить .... Но такого рода вещи действительно являются последним средством ...

Сборка для x86 и запуск Valgrind / Cachegrind для кода для правильного профилирования производительности. Или CCStudio от Texas Instruments имеет приятный профиль . Тогда вы действительно будете знать, на чем сосредоточиться ...


7

Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?

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


7

Не так глубоко или сложно, как предыдущие ответы, но здесь: (это более начальный / средний уровень)

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

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

1
AFAIK, в большинстве случаев, любой разумный оптимизатор будет хорошо работать с циклами, без необходимости явной работы программиста в обратном порядке. Либо оптимизатор обратит сам цикл, либо у него есть другой способ, который одинаково хорош. Я отметил , идентичный вывод ASM для (правда , относительно простого) петель написано как восходящие противами макс и нисходящего против 0. Конечно, мои Z80 дней у меня в привычке рефлекторно пишущих задом петель, но я подозреваю , упоминая его новичок, как правило, Красная сельдь / преждевременная оптимизация, когда читаемый код и изучение более важных практик должны быть приоритетными.
underscore_d

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

5

Невозможно сказать. Это зависит от того, как выглядит код. Если мы можем предположить, что код уже существует, тогда мы можем просто посмотреть на него и выяснить, как его оптимизировать.

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

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

Что ж, и «покажите код на SO и попросите совета по оптимизации для этого конкретного куска кода».


5

Если лучше аппаратное обеспечение, то обязательно сделайте это. В противном случае

  • Убедитесь, что вы используете лучшие варианты компилятора и компоновщика.
  • Если подпрограмма «горячая точка» в другой библиотеке часто используется, рассмотрите возможность ее перемещения или клонирования в модуль вызывающих абонентов. Устраняет некоторые накладные расходы на вызов и может улучшить попадания в кэш (см., Как AIX статически связывает strcpy () в отдельно связанные общие объекты). Это, конечно, может также уменьшить попадания в кеш, поэтому есть одна мера.
  • Посмотрите, есть ли возможность использовать специализированную версию процедуры горячей точки. Недостатком является поддержка более чем одной версии.
  • Посмотри на ассемблере. Если вы думаете, что это может быть лучше, подумайте, почему компилятор не понял этого, и как вы могли бы помочь компилятору.
  • Подумайте: действительно ли вы используете лучший алгоритм? Это лучший алгоритм для вашего размера ввода?

Я бы добавил к вашему первому пар .: не забудьте отключить всю информацию отладки в опциях вашего компилятора .
Варни

5

Путь Google - это один из вариантов: «Кэшируйте его. По возможности, не трогайте диск».


5

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

Узнайте, на что тратится время Узнайте, что именно занимает время. Это файл IO? Это процессорное время? Это сеть? Это база данных? Оптимизировать для IO бесполезно, если это не узкое место.

Знайте свою среду Знание того, где оптимизировать, обычно зависит от среды разработки. Например, в VB6 передача по ссылке медленнее, чем передача по значению, но в C и C ++ по ссылке значительно быстрее. В C разумно попробовать что-то и сделать что-то другое, если код возврата указывает на сбой, в то время как в Dot Net перехват исключений происходит намного медленнее, чем проверка действительного условия перед попыткой.

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

Избегайте поиска Внутри цикла, который нужно оптимизировать, я избегаю каких-либо поисков. Найти смещение и / или индекс вне цикла и повторно использовать данные внутри.

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

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

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

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


5

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


5

Добавление этого ответа, так как я не видел его во всех остальных.

Минимизируйте неявное преобразование между типами и знаком:

Это относится, по крайней мере, к C / C ++. Даже если вы уже думаете, что у вас нет преобразований - иногда полезно проверить добавление предупреждений компилятора вокруг функций, которые требуют производительности, особенно для отслеживания преобразований внутри циклов.

GCC в частности: вы можете проверить это, добавив несколько подробных прагм вокруг вашего кода,

#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

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

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


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

@ Гай справедливо - но во многих случаях смена языков не является реалистичным выбором. Поскольку C / C ++ очень широко используются, полезно сделать их более строгими, даже если они зависят от компилятора.
ideasman42

4

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


4

Твик ОС и фреймворк.

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

Например, Facebook изменил некоторые вещи на уровне ядра в Linux, изменил работу memcached (например, они написали прокси memcached и использовали udp вместо tcp ).

Другой пример этого - Window2008. Win2K8 имеет версию, в которой вы можете установить только базовую ОС, необходимую для запуска приложений X (например, Web-Apps, Server Apps). Это снижает значительную часть накладных расходов, которые ОС несет на выполнение процессов, и повышает производительность.

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


2
Это был бы правильный подход после того, как все другие подходы потерпели неудачу, или если конкретная функция ОС или платформы была ответственна за заметно сниженную производительность, но уровень знаний и контроля, необходимый для этого, может быть недоступен для каждого проекта.
Эндрю Нили
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.