Какие есть микрооптимизации в C? Есть ли кто-нибудь действительно полезный?

Я понимаю большинство существующих микрооптимизаций, но действительно ли они полезны?

Exempli gratia: действительно ли выполнение ++i вместо i++, while(1) или for(;;) приводит к повышению производительности (по отпечаткам памяти или циклам ЦП)?

Итак, вопрос в том, какие микрооптимизации можно сделать в C? Действительно ли они полезны?


person Community    schedule 21.01.2010    source источник
comment
это как минимум 3 вопроса. по крайней мере, один из них: ++ i вместо i ++ здесь неоднократно отвечал. вам может быть лучше перефразировать свой вопрос или сделать его более одного, или этот вопрос может быть закрыт как не настоящий вопрос или дубликат.   -  person ax.    schedule 21.01.2010
comment
Я прояснил вопрос, спасибо axe. @runrunraygun: Что такое вики сообщества?   -  person Mark R.    schedule 21.01.2010
comment
meta.stackexchange.com/questions/11740/   -  person ax.    schedule 21.01.2010
comment
Я не уверен, что это должна быть вики сообщества, но если, по вашему мнению (как опытные пользователи Stack Overflow), вы обнаружите, что это должно быть, не стесняйтесь отмечать это как таковое.   -  person Mark R.    schedule 21.01.2010
comment
Если ваш компилятор C генерирует код, который медленнее для одного из ++ i или i ++ по сравнению с другим, выберите лучший компилятор.   -  person Pascal Cuoq    schedule 21.01.2010
comment
См. stackoverflow.com/questions/ 1447441 / и stackoverflow.com/questions/653980/c-optimization- методы и stackoverflow.com/questions/763656/. Я думаю, что проблемы здесь уже довольно хорошо решены.   -  person dmckee --- ex-moderator kitten    schedule 21.01.2010


Ответы (13)


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

person Community    schedule 21.01.2010
comment
+100. Если вы находитесь в положении, когда подобные микрооптимизации действительно имеют значение, вы бы не задавали этот вопрос. - person DevSolar; 02.03.2010

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

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

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

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

person Community    schedule 21.01.2010
comment
Никогда не беспокойтесь о микрооптимизациях - за это +1. Не говорит, что не делайте их, просто не беспокойтесь о них. Если конкретную микрооптимизацию можно провести, не беспокоясь, например, потому что это ваша вторая натура, то вы можете просто сделать это заранее. Просто не тратьте ресурсы и не накапливайте в будущем «ужасный код». - person Steve Jessop; 21.01.2010

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

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

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

person Community    schedule 21.01.2010
comment
Я не думаю, что компилятор вставляет код [s] для отслеживания текущего значения переменной, это просто вопрос, вставляет ли он инструкцию увеличения до или после блока кода, сгенерированного для выражения. Это должно быть одинаковое количество накладных расходов (приращение, если оно в регистре, загрузка / приращение / сохранение, если нет) независимо. - person TMN; 21.01.2010
comment
Этот пост имеет какое-то отношение. Ознакомьтесь с комментариями для получения информации об использовании предварительного увеличения / уменьшения по сравнению с пост-версиями: appdeveloper.intel.com/en-us/blog/2010/01/11/ - person Xandy; 21.01.2010
comment
По-разному. Конечно, возможно, что компилятор сможет тщательно упорядочить операции для использования того же количества инструкций. Мои эксперименты с C # показывают, что это справедливо для простых примеров, но только потому, что они используют стек оценки, а промежуточные значения хранятся в стеке. Он должен где-то отслеживать предыдущее значение, чтобы использовать предыдущее значение при оценке любого выражения, частью которого оно является. Если до / после инкремента является выражением, то вы правы. Однако возможно, что в сложном выражении может потребоваться временная переменная. - person tvanfosson; 21.01.2010

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

Помните, [отредактированная] преждевременная [/ отредактированная] оптимизация - это зло.

person Community    schedule 21.01.2010
comment
Оптимизация - это не зло, но это всего лишь один аспект вашего кода и, возможно, не самый важный. Иногда важно не выполнять оптимизацию, чтобы сохранить какой-то другой аспект, например, удобочитаемость или безопасность, но если вы можете написать код, который занимает вдвое меньше времени, например, путем кэширования промежуточных результатов, и является таким же чистым и читаемым, вам следует сделать это. Это. С другой стороны, я не собираюсь тратить много времени на улучшение программы, выполнение которой занимает 7 секунд, если только она не должна запускаться каждые 6 секунд. - person tvanfosson; 21.01.2010
comment
Фактическая цитата Кнута: «Мы должны забыть о небольшой эффективности, скажем, примерно в 97% случаев: преждевременная оптимизация - это корень всего зла». - person Leonardo Herrera; 21.01.2010
comment
РАННЯЯ оптимизация - это зло. Заставьте его работать, заставьте его работать правильно, а затем заставьте его работать быстро, именно в таком порядке. - person Mike DeSimone; 21.01.2010
comment
Я понимаю вашу точку зрения, все вы, ребята, имеете право. Я отредактировал свой ответ. - person kokosing; 22.01.2010

Честно говоря, этот вопрос, хотя и актуален, сегодня не актуален - почему?

Авторы компиляторов намного умнее, чем 20 лет назад, они возвращаются назад во времени, тогда эти оптимизации были бы очень актуальны, мы все работали со старыми процессорами 80286/386, и кодеры часто прибегали к уловкам, чтобы сжать еще больше байтов из скомпилированного кода.

Сегодня процессоры слишком быстры, разработчики компиляторов знают тонкие детали инструкций операндов, чтобы все работало, учитывая, что есть конвейерная обработка, основные процессоры, акры ОЗУ, помните, с процессором 80386 было бы 4 Мб ОЗУ и если повезет, 8Мб было сочтено лучше !!

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

Выше я изложил природу процессора и компиляторов, я говорил о семействе процессоров Intel 80x86, компиляторах Borland / Microsoft.

Надеюсь, это поможет, С уважением, Том.

person Community    schedule 21.01.2010
comment
На самом деле, я подозреваю, что писатели компиляторов, вероятно, были лучше 20 лет назад - они должны были быть. С другой стороны, компиляторы сегодня могут быть лучше, но это потому, что нынешние авторы компиляторов стоят на плечах работы более ранних авторов компиляторов и исследователей, которые улучшили состояние дел. - person tvanfosson; 21.01.2010
comment
@tvanfosson: Согласен! Из желудей вырастают большие дубы :) - person t0mm13b; 21.01.2010
comment
если вы пишете на c сегодня, велика вероятность, что он встроен, поэтому вы не можете обязательно полагаться на ресурсы размера x86 - person jk.; 21.01.2010
comment
Основная причина, по которой компиляторы сейчас лучше, заключается в том, что они намного медленнее, потому что мы можем позволить себе в 10 раз больше циклов для компиляции ... тем более для встраиваемых систем, где наши машины разработки в 10 раз больше, чем быстро, как цель (или даже в 1000 раз, как я недавно поразил в одном случае). - person Andrew McGregor; 21.01.2010

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

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

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

person Community    schedule 21.01.2010

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

person Community    schedule 21.01.2010

Для более прагматичного подхода к вопросу ++ i и i ++ (по крайней мере, в контексте C ++) см. http://llvm.org/docs/CodingStandards.html#micro_preincrement.

Если это говорит Крис Латтнер, я должен обращать внимание. ;-)

person Community    schedule 21.01.2010
comment
В C ++ да. В C разницы в производительности не будет. - person Stephen Canon; 21.01.2010

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

Для этих конкретных примеров в C for (;;) - это идиома для бесконечного цикла, а «i ++» - это обычная идиома для «добавить единицу к i», если вы не используете значение в выражении, и в этом случае это зависит от того, значение с наиболее ясным значением - это значение до или после приращения.

person Community    schedule 21.01.2010

По моему опыту, здесь настоящая оптимизация.

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

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

person Community    schedule 02.03.2010

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

person Community    schedule 21.01.2010

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

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

int i;
for (i = 0; i < x; i++)
    doStuff(array[i]);

Но вы могли бы немного оптимизировать, сделав это таким образом -

int i;
for (i = x-1; i != 0; i--)
{
    doStuff(array[i]);
}
doStuff(array[0]);

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

Код примера MaR лучше. Учтите это, предполагая, что doStuff () возвращает int:

int i = x;
while (i != 0)
{
    --i;
    printf("%d\n",doStuff(array[i]));
}

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

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

Если ваш тест похож на (x ‹y), тогда оценка теста будет примерно такой:

  • вычесть y из x, сохраняя результат в некотором регистре r1
  • test r1, чтобы установить флаги n и z
  • ветвление на основе значений флагов n и z

Если ваш тест (x! = 0), вы можете сделать это:

  • test x, чтобы установить флаг z
  • ветвление на основе значения флага z

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

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

person Community    schedule 21.01.2010
comment
Если компилятор может доказать, что порядок не важен, то он сделает это за вас. Компиляторы действительно неплохо справляются с этим преобразованием. - person Stephen Canon; 21.01.2010
comment
int я = х; в то время как (i! = 0) {--i; doStuff (я); } - person MaR; 21.01.2010
comment
Я думаю, что причина этого не в том, что уменьшение происходит быстрее, а в том, что сравнение с 0 не тратит впустую регистр, поскольку некоторые (все обычные?) Процессоры имеют зашитые регистры 0 и 1. Опять же, предполагая, что выборка array [i] из памяти преобладает над doStuff (), вы рассчитываете на то, что предварительная выборка кеша будет работать как с обратными шаблонами доступа, так и с прямыми. Что может быть более серьезной проблемой, чем тратить зря регистр, если это окажется ложным. - person janneb; 21.01.2010
comment
@janneb, нет, это не причина, по которой на некоторых архитектурах уменьшение выполняется быстрее. При уменьшении вам не нужно явно сравнивать с 0, нулевой флаг автоматически поднимается при достижении 0. Может быть другая причина, многие процессоры имеют встроенные команды цикла (x86), которые могут быть быстрее (не всегда, в Pentium Pro / II / III раз LOOP был значительно медленнее, чем комбинация DEC; JNZ. Это говорит о том, что верно, что шаблон загрузки кеша, вероятно, более важен, чем точная комбинация кода. - person Patrick Schlüter; 03.03.2010

person    schedule
comment
Это что? Не могли бы вы дать какое-нибудь объяснение? - person kokosing; 21.01.2010
comment
Поменять местами числа x и y - person laura; 21.01.2010
comment
@Balon: это поведение, определяемое реализацией, оно может работать на одной платформе, на другой - не работать ... лучший способ действий - держаться подальше от этих уловок и не зависеть от них. - person t0mm13b; 21.01.2010
comment
Это примерно в 3 раза медленнее, чем использование временной переменной при включенной оптимизации. stackoverflow.com/questions/1826159/ - person Yacoby; 21.01.2010
comment
Что в нем определяется реализацией? - person Richard Pennington; 21.01.2010
comment
Это ужасно. Это сложно понять, и это приведет к полному замешательству большинства программистов, которым приходится поддерживать такой код. Такой тип кодирования - одна из причин, по которой многие программные проекты терпят неудачу. - person Mick; 21.01.2010
comment
его единственная реализация, определенная, если вы попытаетесь сделать это в одной точке последовательности, как написано, конечно, она даже не компилируется, это также, вероятно, хуже, чем использование temp - person jk.; 21.01.2010
comment
Боже. Астор забыла только про смайлик. - person Richard Pennington; 21.01.2010
comment
@Richard: stackoverflow.com/questions/36906/ - person t0mm13b; 21.01.2010
comment
фактически не определено, если это сделано в одной точке последовательности, для этого вообще ничего не определено - person jk.; 21.01.2010
comment
@ tommieb75: Это не неопределено. И в этом случае x и y не могут быть связаны друг с другом. Если только мы не говорим о C ++ (которым мы не являемся), и это были ссылки. - person Richard Pennington; 21.01.2010
comment
учитывая, что пример даже не компилируется, трудно сказать, будет ли он вызывать UB или нет - person jk.; 21.01.2010
comment
Когда я был ребенком, у меня возникла задача придумать это решение, и я это сделал; Я был так горд собой. Спасибо за воспоминания! - person Leonardo Herrera; 21.01.2010
comment
Да ладно, ребята, здесь мы говорим о микрооптимизации ... Мы все знаем, что вам, вероятно, следует вместо этого сосредоточиться на предоставлении ценности для бизнеса ;-) - person Viktor Klang; 21.01.2010
comment
Астор пригвоздил (ладно, смайлик это не так, но неважно). Это идеальный ответ, чтобы продемонстрировать, как то, что выглядит как оптимизация, только сбивает с толку И к тому же намного медленнее (по крайней мере, на современных процессорах). - person MaR; 21.01.2010
comment
tommieb75: Знаю, только что ответил на вопрос когута :) - person kjagiello; 21.01.2010
comment
-1 Это наихудший вид микрооптимизации, делающий ваш код медленнее (3 чтения, 3 записи, которые нельзя переупорядочить против 2 операций чтения, 2 записи), менее переносимым, менее читаемым. - person Patrick Schlüter; 03.03.2010