Из моего университетского курса я слышал, что по соглашению лучше разместить более вероятное условие в if
, а не в else
, что может помочь предсказателю статической ветки. Например:
if (check_collision(player, enemy)) { // very unlikely to be true
doA();
} else {
doB();
}
можно переписать как:
if (!check_collision(player, enemy)) {
doB();
} else {
doA();
}
Я нашел сообщение в блоге Филиалы с использованием GCC, что объясняет это явление более подробно:
Форвардные ветки генерируются для операторов if. Обоснование что их вряд ли удастся, так это то, что процессор может преимущество того, что инструкции, следующие за ветвью инструкция уже может быть помещена в буфер команд внутри Блок инструкций.
рядом с ним, он говорит (внимание мое):
При написании инструкции if-else всегда заставляйте блок "then" блокировать больше вероятно, будет выполнен, чем блок else, поэтому процессор может принимать преимущество инструкций, уже помещенных в выборку команд буфер.
В конечном счете, есть статья, написанная Intel, Реорганизация ветвей и циклов для предотвращения Mispredicts, которая суммирует это с двумя правилами:
Статическое предсказание ветвления используется, когда нет данных, собранных микропроцессор, когда он встречает ветвь, которая обычно является первый раз встречается ветка. Правила просты:
- Перемещение по умолчанию по умолчанию не принято
- Отключенная ветка по умолчанию принята
Чтобы эффективно писать свой код, чтобы воспользоваться этими правила при написании операторов if-else или switch, проверьте наиболее общие случаи сначала и работать постепенно вниз до наименее общего.
Как я понимаю, идея состоит в том, что конвейерный CPU может следовать инструкциям из кэша команд, не нарушая его, перепрыгивая на другой адрес в сегменте кода. Я знаю, однако, что это может быть значительно упрощено в случае современных микроархитектур процессора.
Однако, похоже, что GCC не соблюдает эти правила. С учетом кода:
extern void foo();
extern void bar();
int some_func(int n)
{
if (n) {
foo();
}
else {
bar();
}
return 0;
}
он генерирует (версия 6.3.0 с -O3 -mtune=intel
):
some_func:
lea rsp, [rsp-8]
xor eax, eax
test edi, edi
jne .L6 ; here, forward branch if (n) is (conditionally) taken
call bar
xor eax, eax
lea rsp, [rsp+8]
ret
.L6:
call foo
xor eax, eax
lea rsp, [rsp+8]
ret
Единственный способ, который я нашел, чтобы заставить желаемое поведение, - переписать условие if
, используя __builtin_expect
следующим образом:
if (__builtin_expect(n, 1)) { // force n condition to be treated as true
поэтому код сборки станет следующим:
some_func:
lea rsp, [rsp-8]
xor eax, eax
test edi, edi
je .L2 ; here, backward branch is (conditionally) taken
call foo
xor eax, eax
lea rsp, [rsp+8]
ret
.L2:
call bar
xor eax, eax
lea rsp, [rsp+8]
ret