Как работают вероятные / маловероятные макросы в ядре Linux и в чем их выгода?
==============================================================================

Я копался в некоторых частях ядра Linux и нашел такие вызовы:

или же

Я нашел их определение:

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

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

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



 что выражение EXP, скорее всего, будет иметь значение C. Возвращаемое значение - EXP.**\_\_ builtin\_expect** предназначен для использования в условном выражении. Почти во всех случаях он будет использоваться в контексте логических выражений, в этом случае гораздо удобнее определить два вспомогательных макроса:

 Эти макросы могут быть использованы как в

Ссылка:[https: //www.akkadia.org/drepper/cpumemory.pd](https://www.akkadia.org/drepper/cpumemory.pdf)



 **Давайте декомпилируем, чтобы узнать, что с ним делает GCC 4.8**

 **Без`__builtin_expect`**

 Компиляция и декомпиляция с GCC 4.8.2 x86\_64 Linux:

Выход

 Порядок инструкций в памяти не изменился: сначала`printf` а потом`puts` и`retq` возвращение

**С`__builtin_expect`**

 Теперь замени`if (i)` с

 и мы получаем:

The`printf` (составлено в`__printf_chk`) был перемещен в самый конец функции после`puts` и возврат для улучшения предсказания ветвлений, как указано в других ответах.

 Так что это в основном так же, как:

 Эта оптимизация не была сделана с`-O0`.

 Но удачи в написании примера, который работает быстрее с`__builtin_expect` чем без, [ЦП действительно умны в те дни](https://stackoverflow.com/a/1851905/895245). Мои наивные попытки[здес](https://github.com/cirosantilli/assembly-cheat/tree/ba3b76cd4530268d4c34e29c354d399c0d8552fc/compiler-generated/gcc/interactive).



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

 Как создаются инструкции ветвления, зависит от архитектуры процессора.



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

GCC использует их для оптимизации прогнозирования ветвлений. Например, если у вас есть что-то вроде следующе

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

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

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

 Существует ряд других стратегий, которые компилятор и процессор могут использовать в этих сценариях. Вы можете найти более подробную информацию о том, как предсказатели веток работают в Википедии:[http: //en.wikipedia.org/wiki/Branch\_predicto](https://en.wikipedia.org/wiki/Branch_predictor)



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

 Например, на процессоре PowerPC неинтифицированная ветвь может занимать 16 циклов, правильно намекаемая 8 и неправильно намекаемая 24. В самых внутренних циклах хорошая хинтинг может иметь огромное значение.

Portability на самом деле не проблема - по-видимому, это определение в заголовке для каждой платформы; Вы можете просто определить «вероятный» и «маловероятный» для платформ, которые не поддерживают статические подсказки ветвлени



 

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

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

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



  [Cody](https://stackoverflow.com/users/4977/cody-brocious), это не имеет ничего общего с Linux, но является подсказкой для компилятора. Что произойдет, будет зависеть от архитектуры и версии компилятора.

 Эта особенность в Linux несколько неправильно используется в драйверах. Как [Osgx](https://stackoverflow.com/users/196561/osgx) указывает на [семантика горячего атрибута](https://stackoverflow.com/questions/15028990/semantics-of-gcc-hot-attribute), любой`hot` или`cold`ункция @, вызываемая в блоке, может автоматически намекнуть, что условие вероятно или нет. Например,`dump_stack()` помечен`cold` так что это избыточно,

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



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

 это можно оптимизировать и во многих компиляторах.

 И, кстати, если вы хотите наблюдать за поведением кода в деталях, вы можете сделать следующее:

gcc -c test.c objdump -d test.o> obj.s

 Тогда открой obj.s, найди ответ.



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

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