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

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

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

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

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

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

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

34 ответа

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 порядка.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

HPH, asoudmove.

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

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

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

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

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

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

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

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

HTH:)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это относится, по крайней мере, к 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

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

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

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

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

Уменьшить переменные размеры (во встроенных системах)

Если ваш переменный размер больше, чем размер слова в конкретной архитектуре, это может оказать значительное влияние как на размер кода, так и на скорость. Например, если у вас есть 16-битная система, и вы используете long int переменная очень часто, а потом понимают, что она никогда не выйдет за пределы диапазона (-32,768 ... 32,767), подумайте о его уменьшении до short int.

По моему личному опыту, если программа готова или почти готова, но мы понимаем, что она занимает около 110% или 120% памяти программного обеспечения целевого оборудования, быстрая нормализация переменных обычно решает проблему чаще, чем нет.

К этому времени оптимизация алгоритмов или частей самого кода может стать бесполезной:

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

Многие люди делают ошибку, имея переменные, которые точно хранят числовое значение единицы, для которой они используют переменную: например, их переменная time хранит точное количество миллисекунд, даже если релевантны только временные шаги, скажем, 50 мс. Возможно, если бы ваша переменная представляла 50 мс для каждого приращения, равного одному, вы могли бы вписаться в переменную, меньшую или равную размеру слова. Например, в 8-битной системе даже простое добавление двух 32-битных переменных генерирует значительный объем кода, особенно если у вас мало регистров, в то время как 8-битные дополнения являются небольшими и быстрыми.

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

Другие вопросы по тегам