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

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

Предположим:

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

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

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

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

550
задан jerryjvl 29 мая '09 в 17:26
источник поделиться
34 ответов
  • 1
  • 2

ОК, вы определяете проблему, где, казалось бы, нет места для улучшения. Это довольно редко, по моему опыту. Я попытался объяснить это в статье доктора Доббса в ноябре 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): типы вопросов, которые я так часто вижу на SO.

P.S. Может возникнуть вопрос, почему я не использовал профилировщик. Ответ заключается в том, что почти каждая из этих "проблем" была сайтом вызова функции, который выбирается из выборок стека. Профилировщики, даже сегодня, едва сводятся к мысли о том, что инструкции и инструкции по вызову более важны для определения и облегчения исправления, чем целые функции. Я на самом деле построил профилировщик, чтобы сделать это, но для реальной сдержанной и интимной близости с тем, что делает код, нет никакой замены для того, чтобы вставлять в него свои пальцы. Не проблема, что количество выборок невелико, потому что ни одна из найденных проблем не настолько мала, что их легко пропустить.

ADDED: 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.

EDIT 2011/11/26: В настоящее время существует проект sourceforge, содержащий исходный код в Visual С++ и подробное описание того, как он был настроен. Он проходит только в первой половине описанного выше сценария, и он не соответствует точно такой же последовательности, но все равно получает ускорение на 2-3 порядка.

397
ответ дан Mike Dunlavey 29 мая '09 в 22:41
источник поделиться

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

  • Предварительно вычислить, а не переучитывать: любые циклы или повторяющиеся вызовы, содержащие вычисления с относительно ограниченным диапазоном входных данных, рассмотрите возможность поиска (массив или словарь), который содержит результат это вычисление для всех значений в допустимом диапазоне входов. Затем используйте простой поиск внутри алгоритма.
    Нижние стороны: если на самом деле используются некоторые из предварительно вычисленных значений, это может ухудшить ситуацию, и поиск может занять значительную память.
  • Не используйте методы библиотеки. Большинство библиотек необходимо писать для правильной работы в широком диапазоне сценариев и выполнять нулевые проверки параметров и т.д. Повторно реализуя метод, вы можете быть в состоянии разделить много логики, которая не применяется в точном обстоятельстве, вы используете его.
    Внизу: запись дополнительного кода означает большую площадь поверхности для ошибок.
  • Использовать библиотечные методы: чтобы противоречить самому себе, языковые библиотеки написаны людьми, которые намного умнее вас или меня; Скорее всего, они делали это лучше и быстрее. Не выполняйте его самостоятельно, если вы не можете сделать это быстрее (т.е.: всегда измеряйте!)
  • Cheat: в некоторых случаях, хотя для вашей проблемы может существовать точный расчет, вам может не понадобиться "точное", иногда приближение может быть "достаточно хорошим" и намного быстрее в сделке. Спросите себя, действительно ли имеет значение, если ответ на 1%? 5%? даже 10%?
    Вниз: Ну... ответ будет не точным.
176
ответ дан jerryjvl 29 мая '09 в 17:38
источник поделиться

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

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

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

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

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

156
ответ дан kenj0418 30 мая '09 в 1:01
источник поделиться

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

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

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

  • Установите компилятор для вывода списков сборки и посмотрите, что он испускает для функций хот-спота в вашем коде. Все эти умные оптимизации, которые "хороший компилятор должен сделать для вас автоматически"? Скорее всего, ваш настоящий компилятор их не делает. Я видел, как GCC генерирует действительно WTF-код.
131
ответ дан Crashworks 30 мая '09 в 1:19
источник поделиться

Бросьте на него больше аппаратного обеспечения!

76
ответ дан sisve 29 мая '09 в 17:32
источник поделиться

Дополнительные предложения:

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

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

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

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

56
ответ дан jerryjvl 10 июня '09 в 17:36
источник поделиться

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

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

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

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

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

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

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

Никогда не помещайте петлю любого типа в триггер!

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

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

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

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

46
ответ дан HLGEM 30 мая '09 в 0:41
источник поделиться

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

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

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

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

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

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

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

29
ответ дан Mats N 19 июня '09 в 19:51
источник поделиться
  • На каком оборудовании вы работаете? Можете ли вы использовать оптимизацию для платформы (например, векторизация)?
  • Можете ли вы получить лучший компилятор? Например. переключиться с GCC на Intel?
  • Можете ли вы запустить параллельный алгоритм?
  • Можете ли вы уменьшить промахи в кэше путем реорганизации данных?
  • Вы можете отключить утверждения?
  • Микро-оптимизация для вашего компилятора и платформы. В стиле "в if/else сначала ставьте наиболее распространенное утверждение"
25
ответ дан Johan Kotlinski 29 мая '09 в 17:42
источник поделиться
  • Встроенные процедуры (исключить вызов/возврат и нажатие параметров)
  • Попробуйте исключить тесты/переключатели с помощью табличного поиска (если они быстрее)
  • Развернуть петли (устройство Duff) до точки, где они просто вписываются в кеш процессора
  • Локализовать доступ к памяти, чтобы не вставлять кеш
  • Локализовать связанные вычисления, если оптимизатор еще этого не делает,
  • Устранить инварианты цикла, если оптимизатор еще не делает этого
15
ответ дан plinth 29 мая '09 в 18:05
источник поделиться

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

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

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

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

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

ПВД, asoudmove.

15
ответ дан asoundmove 26 янв. '11 в 7:35
источник поделиться

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

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

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

15
ответ дан none 30 мая '09 в 0:58
источник поделиться

Разделить и покорить

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

12
ответ дан MPelletier 22 марта '10 в 6:21
источник поделиться
  • Когда вы дойдете до того, что используете эффективные алгоритмы, это вопрос о том, что вам нужно больше скорости или памяти. Используйте кеширование для "оплаты" в памяти для большей скорости или использования вычислений для уменьшения объема памяти.
  • Если возможно (и более экономически выгодно) бросить оборудование при проблеме - быстрее процессор, больше памяти или HD может решить проблему быстрее, чем пытаться ее кодировать.
  • Использовать распараллеливание, если это возможно - выполнить часть кода для нескольких потоков.
  • Использовать правильный инструмент для задания. некоторые языки программирования создают более эффективный код, используя управляемый код (т.е. Java/.NET), ускоряют разработку, но родные языки программирования создают более быстрый исполняемый код.
  • Микро оптимизация. Только применимые вы можете использовать оптимизированную сборку для быстрой обработки небольших фрагментов кода, использование оптимизаций SSE/векторов в правильных местах может значительно повысить производительность.
12
ответ дан Dror Helper 30 мая '09 в 18:39
источник поделиться

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

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

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

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

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

HTH:)

11
ответ дан gnat 29 июля '11 в 6:28
источник поделиться

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

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

8
ответ дан Killroy 13 сент. '09 в 19:07
источник поделиться

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

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

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

8
ответ дан Steve Wortham 30 мая '09 в 1:00
источник поделиться

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
ответ дан Sam 29 янв. '11 в 5:23
источник поделиться

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

7
ответ дан dschwarz 29 мая '09 в 17:32
источник поделиться

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

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

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

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

7
ответ дан Cwaig 11 авг. '09 в 2:59
источник поделиться

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

  • Очевидное: сухое
  • запустите циклы назад, чтобы вы всегда сравнивали с 0, а не с переменной
  • использовать побитовые операторы всякий раз, когда вы можете
  • перерыв повторяющегося кода в модули/функции
  • объекты кэша
  • Локальные переменные имеют небольшое преимущество в производительности.
  • максимально ограничить обработку строк
6
ответ дан Aaron 30 авг. '12 в 19:01
источник поделиться

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

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

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

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

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

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

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

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

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

6
ответ дан Pat 17 апр. '12 в 7:03
источник поделиться

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

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

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

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

5
ответ дан jalf 29 мая '09 в 18:10
источник поделиться

Путь Google - это один из вариантов "Cache it.. По возможности не касайтесь диска"

5
ответ дан asyncwait 09 окт. '09 в 14:01
источник поделиться

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

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

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

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

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

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

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

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

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

5
ответ дан Andrew Neely 20 июля '11 в 16:15
источник поделиться

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

  • Проверьте, что вы используете лучшие параметры компилятора и компоновщика.
  • Если функция hotspot в другой библиотеке часто используется для вызова вызывающего абонента, подумайте о перемещении или клонировании ее в модуль вызывающих абонентов. Устраняет некоторые из служебных задач вызова и может улучшить хиты кэша (cf как AIX привязывает strcpy() статически к отдельно связанным общим объектам). Это, конечно же, может уменьшить кеш-хиты, поэтому одна мера.
  • Посмотрите, есть ли возможность использовать специализированную версию процедуры hotspot. Нижняя сторона поддерживает более одной версии.
  • Посмотрите на ассемблер. Если вы считаете, что это может быть лучше, подумайте, почему компилятор не понял этого и как вы могли помочь компилятору.
  • Подумайте: вы действительно используете лучший алгоритм? Это лучший алгоритм для вашего размера ввода?
5
ответ дан mealnor 29 мая '09 в 18:21
источник поделиться

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

4
ответ дан Nosredna 30 мая '09 в 1:20
источник поделиться

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

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

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

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

#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

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

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

4
ответ дан ideasman42 05 окт. '13 в 10:22
источник поделиться

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

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

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

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

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

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

4
ответ дан vsz 25 июня '11 в 19:03
источник поделиться

Подгоняйте ОС и инфраструктуру.

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

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

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

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

4
ответ дан Nir Levy 13 сент. '09 в 19:12
источник поделиться
  • 1
  • 2

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