Кодирование Методов, которые позволяют компилятору/оптимизатору сделать более быструю программу

Много лет назад компиляторы C не были особенно умны. Как обходное решение K&R изобрел ключевое слово регистра, для вывода подсказок компилятору, это, возможно, это будет хорошая идея сохранить эту переменную во внутреннем регистре. Они также сделали третичный оператор, чтобы помочь сгенерировать лучший код.

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

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

Какие кодирующие методы доступны, который может позволить компилятору/оптимизатору сгенерировать более быстрый код?

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

Вот связанный вопрос

[Редактирование] Этот вопрос не о полном процессе, чтобы представить, и оптимизировать. Предположите, что программа была записана правильно, скомпилирована с полной оптимизацией, протестированной и введенной в эксплуатацию. Могут быть конструкции в Вашем коде, которые мешают оптимизатору делать лучшее задание, что это может. Что можно сделать для рефакторинга, который удалит эти запреты и позволит оптимизатору генерировать еще более быстрый код?

[Редактирование] ссылка по теме Смещения

116
задан 11 revs, 2 users 95% 23 May 2017 в 12:10
поделиться

28 ответов

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

void DoSomething(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)
{
    for (int i=0; i<numFoo, i++)
    {
         barOut.munge(foo1, foo2[i]);
    }
}

, компилятор не знает, что Foo1! = Boinout, и, таким образом, приходится перезагружать Foo1 каждый раз через петлю. Он также не может прочитать foo2 [i] до тех пор, пока не закончится запись в бараут. Вы могли бы начать возиться с ограниченными указателями, но это так же эффективно (и гораздо яснее), чтобы сделать это:

void DoSomethingFaster(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)
{
    Foo barTemp = barOut;
    for (int i=0; i<numFoo, i++)
    {
         barTemp.munge(foo1, foo2[i]);
    }
    barOut = barTemp;
}

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

54
ответ дан 24 November 2019 в 02:11
поделиться

Не выполняйте одну и ту же работу снова и снова!

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

void Function()
{
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomething();
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomethingElse();
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomethingCool();
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomethingReallyNeat();
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomethingYetAgain();
}

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

void Function()
{
   MySingleton* s = MySingleton::GetInstance();
   AggregatedObject* ao = s->GetAggregatedObject();
   ao->DoSomething();
   ao->DoSomethingElse();
   ao->DoSomethingCool();
   ao->DoSomethingReallyNeat();
   ao->DoSomethingYetAgain();
}

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

4
ответ дан 24 November 2019 в 02:11
поделиться

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

Тщательно прочитайте руководство по руководству и понять, что он говорит вам. Используйте компилятор в его максимально.

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

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

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

С уважением

Марка

Когда я впервые подбираю кусок кода, я обычно могу получить в 1,4 - в 2,0 раза больше производительности (то есть новая версия кода выполняется в 1/14 или 1 / 2 Время старой версии) в течение дня или двух за счет возобновления с флагами компилятора. Предоставлено, что может быть комментарий к отсутствию компилятора Свободы среди ученых, которые возникают большую часть кода, на котором я работаю, а не симптом моего превосходства. Установив флаги компилятора на MAX (и редко просто -O3) он может занять месяцы тяжелой работы, чтобы получить еще один раз в 1,05 или 1,1

2
ответ дан 24 November 2019 в 02:11
поделиться

Оптимизатор на самом деле не контролирует производительность вашей программы, вы находитесь. Используйте соответствующие алгоритмы и структуры и профиль, профиль, профиль.

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

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

Что приводит к следующему пункту , прочитайте ручное руководство ^ # $ ! GCC может представлять векторизировать простое C-код C, если вы посыпьте __ __ здесь и атрибут __ __ (__Aligned__) . Если вы хотите что-то очень специфичное от оптимизатора, вам может быть конкретно.

26
ответ дан 24 November 2019 в 02:11
поделиться

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

В этом документе находятся множество других советов по оптимизации: Оптимизация CPP (немного старый документ)

. Основные характеристики:

  • Использование списков инициализации конструкции
  • Использование префиксных операторов
  • Явные конструкторы
  • Встроенные функции
  • Избегайте временных объектов
  • Будьте в курсе стоимости виртуальных функций
  • Обратные объекты через ссылочные параметры
  • Рассмотрим на распределение классов
  • Рассмотрим распределение контейнера STL
  • «Пустой элемент» оптимизация
  • и т. Д.
11
ответ дан 24 November 2019 в 02:11
поделиться

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

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

Далее, профиль ваш код.

Тогда и только тогда вы можете начать расследование последствий отказа от компилятора, как использовать память. Сделайте 1 изменение за раз и измерить его влияние.

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

74
ответ дан 24 November 2019 в 02:11
поделиться

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

0
ответ дан 24 November 2019 в 02:11
поделиться
Я думаю, что объекты в модели домена являются объектами POCO и что они должны быть сопоставлены с объектами рамки сущностей или из них. Это хорошее решение?

Думаю, так и есть.

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

-121--2467930-

Одна оптимизация, которую я использовал в C++, создает конструктор, который ничего не делает. Для перевода объекта в рабочее состояние необходимо вручную вызвать init ().

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

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

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

1
ответ дан 24 November 2019 в 02:11
поделиться

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

Оптимизатор поможет производительность вашей программы незначительно.

1
ответ дан 24 November 2019 в 02:11
поделиться

Когда DEC вышла с помощью альфа-процессоров, была рекомендация сохранить количество аргументов функции до 7 до 7 лет, так как компилятор всегда старается автоматически поставить до 6 аргументов в регистр.

2
ответ дан 24 November 2019 в 02:11
поделиться

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

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

2
ответ дан 24 November 2019 в 02:11
поделиться

Как изменить даты ZERO на NULL.

-121--4859798-

Простое описание SDK показывает, что ответ заключается в том, что это не имеет значения - они одинаковы . Оба они превращаются в __ T (x) .

C:\...\Visual Studio 8\VC>findstr /spin /c:"#define _T(" *.h 
crt\src\tchar.h:2439:#define _T(x)       __T(x) 
include\tchar.h:2390:#define _T(x)       __T(x)

C:\...\Visual Studio 8\VC>findstr /spin /c:"#define _TEXT(" *.h 
crt\src\tchar.h:2440:#define _TEXT(x)    __T(x) 
include\tchar.h:2391:#define _TEXT(x)    __T(x)

И для полноты:

C:\...\Visual Studio 8\VC>findstr /spin /c:"#define __T(" *.h 
crt\src\tchar.h:210:#define __T(x)     L ## x 
crt\src\tchar.h:889:#define __T(x)      x 
include\tchar.h:210:#define __T(x)     L ## x 
include\tchar.h:858:#define __T(x)      x

Однако, технически , для C++ следует использовать TEXT () вместо _ TEXT () , но он (в конечном итоге) расширяется и на то же самое.

-121--1404155-
  1. Используйте максимально возможную локальную область для всех объявлений переменных.

  2. Используйте const по возможности

  3. Dont используйте регистр, если вы не планируете профилировать как с ним, так и без него

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

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

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

3
ответ дан 24 November 2019 в 02:11
поделиться

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

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

11
ответ дан 24 November 2019 в 02:11
поделиться

На большинстве современных процессоров самая большая узкое место - это память.

Псевдонимы: HIT-магазин нагрузки может быть разрушительным в тесной петле. Если вы читаете одно местоположение памяти и пишете в другое и знаете, что они несерьезно, осторожно нанесение ключевого слова Alias ​​на параметры функции действительно могут помочь компилятору генерировать более быстрый код. Однако, если регионы памяти совпадают, и вы использовали «псевдоним», вы находитесь для хорошей сеансы отладки неопределенного поведения!

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

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

18
ответ дан 24 November 2019 в 02:11
поделиться

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

-121--2722714-

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

Пример:

int fac2(int x, int cur) {
  if (x == 1) return cur;
  return fac2(x - 1, cur * x); 
}
int fac(int x) {
  return fac2(x, 1);
}

Конечно, этот пример не имеет никаких результатов проверки.

Позднее редактировать

, пока у меня нет прямых знаний о коде; Кажется ясно, что требования к использованию CTE на SQL Server были специально разработаны так, что она может оптимизировать через рекурсию для хвоста.

4
ответ дан 24 November 2019 в 02:11
поделиться

Я на самом деле видел, что это сделано в SQLite, и они утверждают, что он приводит к повышению производительности ~ 5%: поместите весь свой код в один файл или используйте препроцессор, чтобы сделать эквивалентное это. Таким образом, оптимизатор будет иметь доступ ко всей программе и может сделать больше интерпретирующих оптимизаций.

4
ответ дан 24 November 2019 в 02:11
поделиться

Заказ, который вы пересекаете память, могут иметь глубокие воздействия на производительность, и компиляторы на самом деле не очень хорошо понять и исправить его. Вы должны быть сознательными на местности кэша, когда вы пишете код, если вы заботитесь о производительности. Например, двумерные массивы в C выделяются в строке основного формата. Пересекающие массивы в Column Chara Formate, будут иметь тенденцию сделать, чтобы у вас больше кэш-памяти и сделайте вашу программу больше, чем привязанность к памяти, чем граница процессора:

#define N 1000000;
int matrix[N][N] = { ... };

//awesomely fast
long sum = 0;
for(int i = 0; i < N; i++){
  for(int j = 0; j < N; j++){
    sum += matrix[i][j];
  }
}

//painfully slow
long sum = 0;
for(int i = 0; i < N; i++){
  for(int j = 0; j < N; j++){
    sum += matrix[j][i];
  }
}
47
ответ дан 24 November 2019 в 02:11
поделиться

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

Алгоритмы, используемые для управления кучей, новеренно медленные в некоторых платформах (например, vxworks). Еще хуже, время, которое требуется, чтобы вернуться с звонка в Malloc, сильно зависит от текущего состояния кучи. Следовательно, любая функция, которая называет MALLOC, собирается сделать удар производительности, для которого нельзя легко учитывать. Этот удар производительности может быть минимальным, если куча все еще чистая, но после того, как устройство работает некоторое время, в то время как куча может стать фрагментированной. Звонки собираются занять дольше, и вы не можете легко рассчитать, как производительность будет ухудшаться с течением времени. Вы не можете действительно производить худшую оценку случаев. Оптимизатор не может предоставить вам любую помощь в этом случае. Чтобы сделать вопросы еще хуже, если куча становится слишком сильно фрагментированной, вызовы начнут в целом. Решением является использование пулов памяти (например, ломтики Glib ) вместо кучи. Вызывы распределения будут намного быстрее и детерминированы, если вы сделаете это правильно.

7
ответ дан 24 November 2019 в 02:11
поделиться

Я использую Intel компилятор. на Windows и Linux.

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

Если код является вычислительным и содержит множество петлей - векторизация отчета в Intel Compiler очень полезно - ищите «VEC-отчет» в помощи.

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

1
ответ дан 24 November 2019 в 02:11
поделиться

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

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

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

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

После этого микрооптимизация (горячих точек) может дать вам хорошую отдачу.

1
ответ дан 24 November 2019 в 02:11
поделиться

Общие оптимизации

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

Объявляйте небольшие функции как встроенные или макросы

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

Удалить мертвый и избыточный код

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

Упростите разработку алгоритмов

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

Развертывание цикла

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

Правка: предоставляют пример развертывания цикла До:

unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
    sum += *buffer++;
}

После развертывания:

unsigned int sum = 0;
size_t i = 0;
**const size_t STATEMENTS_PER_LOOP = 8;**
for (i = 0; i < BYTES_TO_CHECKSUM; **i = i / STATEMENTS_PER_LOOP**)
{
    sum += *buffer++; // 1
    sum += *buffer++; // 2
    sum += *buffer++; // 3
    sum += *buffer++; // 4
    sum += *buffer++; // 5
    sum += *buffer++; // 6
    sum += *buffer++; // 7
    sum += *buffer++; // 8
}
// Handle the remainder:
for (; i < BYTES_TO_CHECKSUM; ++i)
{
    sum += *buffer++;
}

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

У меня были потрясающие результаты, когда я развернул цикл до 32 операторов. Это было одним из узких мест, поскольку программе приходилось вычислять контрольную сумму для файла размером 2 ГБ. Эта оптимизация в сочетании с чтением блоков повысила производительность с 1 часа до 5 минут. Развертывание цикла также обеспечивало отличную производительность на языке ассемблера, мой memcpy был намного быстрее, чем компилятор memcpy . - Т.

Уменьшение операторов if

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

Логическая арифметика ( Отредактировано: применил формат кода к фрагменту кода, добавлен пример)

Преобразование операторов if в логические присваивания. Некоторые процессоры могут условно выполнять инструкции без ветвления:

bool status = true;
status = status && /* first test */;
status = status && /* second test */;

короткое замыкание оператора логического И ( && ) предотвращает выполнение тестов, если ] status равен false .

Пример:

struct Reader_Interface
{
  virtual bool  write(unsigned int value) = 0;
};

struct Rectangle
{
  unsigned int origin_x;
  unsigned int origin_y;
  unsigned int height;
  unsigned int width;

  bool  write(Reader_Interface * p_reader)
  {
    bool status = false;
    if (p_reader)
    {
       status = p_reader->write(origin_x);
       status = status && p_reader->write(origin_y);
       status = status && p_reader->write(height);
       status = status && p_reader->write(width);
    }
    return status;
};

Факторное распределение переменных вне циклов

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

Выражения факторных констант вне циклов

Если вычисление или значение переменной не зависит от индекса цикла, переместите его за пределы (до) цикла.

Ввод-вывод в блоках

Чтение и запись данных большими порциями (блоками). Больше лучше. Например,чтение одного октета за раз менее эффективно, чем чтение 1024 октетов за одно чтение.
Пример:

static const char  Menu_Text[] = "\n"
    "1) Print\n"
    "2) Insert new customer\n"
    "3) Destroy\n"
    "4) Launch Nasal Demons\n"
    "Enter selection:  ";
static const size_t Menu_Text_Length = sizeof(Menu_Text) - sizeof('\0');
//...
std::cout.write(Menu_Text, Menu_Text_Length);

Эффективность этого метода можно продемонстрировать визуально. : -)

Не используйте printf семейство для постоянных данных

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

Отформатируйте в память, затем запишите

Format в массив char , используя несколько sprintf , затем используйте fwrite . Это также позволяет разбить структуру данных на «постоянные разделы» и переменные разделы. Подумайте о mail-merge .

Объявить постоянный текст (строковые литералы) как static const

Когда переменные объявляются без static , некоторые компиляторы могут выделять место в стеке и копировать данные из ПЗУ. Это две ненужные операции. Это можно исправить с помощью статического префикса .

Наконец, код, подобный компилятору.

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

36
ответ дан 24 November 2019 в 02:11
поделиться

Две техники кодирования, которые я не видел в приведенном выше списке:

Обойти компоновщик, написав код как уникальный источник

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

Но если вы хорошо спроектируете свою программу, вы также можете скомпилировать ее из уникального общего источника. То есть вместо компиляции unit1.c и unit2.c затем свяжите оба объекта, скомпилируйте all.c, который просто #include unit1.c и unit2.c. Таким образом, вы получите выгоду от всех оптимизаций компилятора.

Это очень похоже на написание программ только с заголовками на C ++ (и даже проще на C).

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

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

Как и ключевое слово register, этот трюк может скоро стать устаревшим. Оптимизация с помощью компоновщика начинает поддерживаться компиляторами gcc: Оптимизация времени компоновки .

Разделяйте атомарные задачи в циклах

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

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

5
ответ дан 24 November 2019 в 02:11
поделиться

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

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

Воспользуйтесь почти бесплатным сравнением с 0, которое большинство процессоров предоставляют вам при выполнении математических или логических операций. Вы почти всегда получаете флаг для == 0 и <0, из которого вы легко можете получить 3 условия:

x= f();
if(!x){
   a();
} else if (x<0){
   b();
} else {
   c();
}

почти всегда дешевле, чем проверка других констант.

Еще одна уловка - использовать вычитание, чтобы исключить одно сравнение при тестировании диапазона.

#define FOO_MIN 8
#define FOO_MAX 199
int good_foo(int foo) {
    unsigned int bar = foo-FOO_MIN;
    int rc = ((FOO_MAX-FOO_MIN) < bar) ? 1 : 0;
    return rc;
} 

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

При использовании строковых функций в c (strcpy, memcpy, ...) помните, что они возвращают - адрес назначения! Часто можно получить лучший код, «забыв» свою копию указателя на пункт назначения и просто вернув ее из возврата этих функций.

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

foo_t * make_foo(int a, int b, int c) {
        foo_t * x = malloc(sizeof(foo));
        if (!x) {
             // return NULL;
             return x; // x is NULL, already in the register used for returns, so duh
        }
        x->a= a;
        x->b = b;
        x->c = c;
        return x;
}

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

(приемы, которые я вспомнил позже)

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

9
ответ дан 24 November 2019 в 02:11
поделиться

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

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

1
ответ дан 24 November 2019 в 02:11
поделиться

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

Проблема в этом случае заключалась в том, что программное обеспечение, которое мы использовали, выделяло тонны небольших ресурсов. Например, выделить четыре байта здесь, шесть байтов там и т. Д. Множество мелких объектов тоже работают в диапазоне 8-12 байтов. Проблема была не столько в том, что программе нужно было много мелочей, а в том, что она выделяла множество мелочей индивидуально, что увеличивало каждое выделение (на этой конкретной платформе) до 32 байтов.

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

Другая часть решения заключалась в замене безудержного использования управляемых вручную элементов char * строкой SSO (оптимизация малых строк).Минимальное выделение составляет 32 байта, я создал строковый класс, который имел встроенный 28-символьный буфер за символом *, поэтому 95% наших строк не нуждались в дополнительном распределении (а затем я вручную заменил почти все появления char * в этой библиотеке с этим новым классом, было весело или нет). Это также помогло тонне фрагментации памяти, которая затем увеличила локальность ссылок для других объектов, на которые указали, и аналогичным образом увеличилась производительность.

3
ответ дан 24 November 2019 в 02:11
поделиться

Хотя это старый вопрос, кто-то другой, вероятно, придумает его в поиске Google.

К сожалению, django serializer предлагает довольно мало настройки, как вы определили. Мое решение, если вы знаете, что будете использовать много сериализации для вашего проекта, было просто скопировать материал сериализации джанго в мой собственный проект и внести некоторые небольшие изменения. Это не идеально, но это делает работу. В частности, для удаления pk 'ов существует строка в start_object (self, obj):

self.xml.startElement("object", {
        "pk"    : smart_unicode(obj._get_pk_val()),
        "model" : smart_unicode(obj._meta),
    })

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

Надеюсь, это кому-то поможет.

-121--2200547-

У меня была та же проблема в проекте библиотеки классов, который я обновил с VS 2008 до VS 2010 Beta 2. Я не добавлял в проект никаких методов расширения, пока после модернизации не увидел ту же самую ошибку.

Добавление класса со следующим кодом в проект решило проблему:

namespace System.Runtime.CompilerServices
{
    public class ExtensionAttribute : Attribute { }
}

Нашел наконечник в этом блоге: http://blog.flexforcefive.com/?p=105

-121--986077-

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

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

for (some silly loop)
if (something)
    if (somthing else)
        if (somthing else)
            if (somthing else)
                /* This is the normal expected case */ 
            else error 4
        else error 3
    else error 2
else error 1

. Для head и ifs могут поместиться в блок кэша, что в теории может привести к более быстрому выполнению цикла.

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

Комментарии? Мне снится сон?

0
ответ дан 24 November 2019 в 02:11
поделиться

Пусть оптимизатор делает свою работу.

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

-4
ответ дан 24 November 2019 в 02:11
поделиться