Подтвердить что ты не робот

Как далеко продвинулся GCC __builtin_expect?

Отвечая на другой вопрос, мне стало интересно. Я хорошо знаю, что

if( __builtin_expect( !!a, 0 ) ) {
    // not likely
} else {
    // quite likely
}

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

Но работает ли это для a) inline ifs, b) переменные и c) значения, отличные от 0 и 1? т.е. будет

__builtin_expect( !!a, 0 ) ? /* unlikely */ : /* likely */;

или

int x = __builtin_expect( t / 10, 7 );
if( x == 7 ) {
    // likely
} else {
    // unlikely
}

или

if( __builtin_expect( a, 3 ) ) {
    // likely
    // uh-oh, what happens if a is 2?
} else {
    // unlikely
}

имеют какой-либо эффект? И все это зависит от целевой архитектуры?

4b9b3361

Ответ 1

Но работает ли это для a) inline ifs, b) переменные и c) значения, отличные от 0 и 1?

Он работает для контекста выражения, который используется для определения ветвления.

Итак, а) Да. b) Нет. c) Да.

И все это зависит от целевой архитектуры?

Оп!

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

(если кто-то может прояснить ту магию, которая также будет большой).

( "Прогнозирование ветвей" усложняет это описание, поэтому я намеренно его пропускаю)

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

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

Ответ 2

Вы прочитали документацию GCC?

Встроенная функция: long __builtin_expect (long exp, long c)

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

Возвращаемое значение - это значение exp, которое должно быть интегральным выражение. Семантика встроенных заключается в том, что ожидается, что exp == c. Например:

if (__builtin_expect (x, 0))
    foo ();

указывает, что мы не ожидаем называть foo, так как мы ожидаем, что x будет равен нулю. Поскольку вы ограничены интегральными выражениями для exp, вы должны использовать такие конструкции, как

if (__builtin_expect (ptr != NULL, 1))
    foo (*ptr);

при тестировании указателей или значений с плавающей запятой.

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

if (x == 0)
    return 10 * y;
else
    return 39;

В машинном коде CPU обычно можно попросить "перейти" к другой строке (которая требует времени и в зависимости от процессора может предотвратить другие оптимизации выполнения, т.е. ниже уровня машинного кода - например, см. заголовок "Филиалы" под http://en.wikipedia.org/wiki/Instruction_pipeline) или вызвать какой-то другой код, но на самом деле нет понятия if/else, где оба истинного и ложного кода равны... вам нужно отделиться, чтобы найти код для одного или другого. Как это делается в основном, в псевдокоде:

test whether x is 0
if it was goto else_return_39
return 10 * y
else_return_39:
return 39

Учитывая, что большинство процессоров медленнее следуют за goto до метки else_return_39:, чем просто проваливаются до return 10 * y, код для ветки "true" будет достигнут быстрее, чем для ложной ветки. Конечно, машинный код может проверить, не является ли x не 0, сначала поставить "ложный" код (return 39) и тем самым изменить характеристики производительности.

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

Но работает ли это для a) inline ifs, b) переменные и c) значения, отличные от 0 и 1?

a) Независимо от того, включена ли окружающая функция или нет, нет необходимости в ветвлении, где появляется оператор if (если оптимизатор не видит условие, что тесты операторов if всегда true или false и только одна ветка не могла работать). Таким образом, это в равной степени применимо к встроенному коду.

[Ваш комментарий показывает, что вас интересовали условные выражения - a ? b : c - Я не уверен - есть спорный ответ на этот вопрос в Могу ли я использовать GCC __builtin_expect() с тернарный оператор в C, который может оказаться проницательным так или иначе или основой для дальнейшего исследования]

b) переменные - вы постулировали:

int x = __builtin_expect( t / 10, 7 );
if( x == 7 ) {

Это не сработает - компилятор не обязан связывать такие ожидания с переменными и помнить их при следующем просмотре if. Вы можете проверить это (как я сделал для gcc 3.4.4), используя gcc -S для вывода вывода ассемблера: сборка не изменяется независимо от ожидаемого значения.

c) значения, отличные от 0 и 1

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

вы должны использовать такие конструкции, как

if (__builtin_expect (ptr != NULL, 1))
    foo (*ptr);

при тестировании указателей или значений с плавающей запятой.

Почему? Ну, если тип указателя больше, чем long, то вызов __builtin_conversion(long, long) будет эффективно отрубать некоторые из менее значимых бит и не сможет включить остальных в тест. Точно так же значения с плавающей запятой могут быть больше, чем длинные, и преобразование не дает ожидаемого результата. Используя логическое выражение, такое как ptr != NULL (данный true преобразуется в 1L и false в 0), вы обязательно получите намеченные результаты.