Где следует разместить утверждение «вероятное/маловероятное», чтобы повысить производительность?
Некоторое программное обеспечение (часто ориентированное на производительность, например ядро Linux, DPDK) имеет помощники C для влияния на предсказание ветвей .
У меня есть абсолютно простой фрагмент кода ( предположим, я знаю процент a > b), чтобы представить проблему вложения и применения условий.likely
/unlikely
когда некоторая логика вложена:
bool foo()
{
foo1(1);
foo2(2);
/* if (unlikely(a > b)) */
/* if (a > b)*/
{
puts("Ohhh!!! Rare case");
return true;
}
return false;
}
int main(void)
{
/* if (unlikely(foo())) */
/* if (foo()) */
{
puts("Azaza");
}
}
Итак, какие две строки следует раскомментировать для большей производительности с теоретической точки зрения?
Очевидно, есть три способа помочь компилятору в прогнозировании ветвления:
1.
2.if (a > b)
if (unlikely(foo()))
3.if (unlikely(a > b))
...
if (foo())
Что теоретически наиболее эффективно и почему?
1 ответ
Насколько мне известно, операторы показывают лучший эффект, если условная переменная отсутствует в кеше. Позвольте мне объяснить более подробно.
В вашем коде процессор все равно должен выполняться. Итакlikely
здесь не будет иметь сильного эффекта, поскольку ни один путь кода не может быть пропущен во время спекулятивного выполнения. Функция должна быть выполнена. Предположим, вы сохранили результатfoo
раньше в переменной, и код выглядит так:
int x = foo();
if (likely(x))
{
puts("Azaza");
}
В этом случаеlikely(x)
вероятно, повлияет только на предварительную выборку/декодер, поскольку процессор только что вычислил x, и значение, скорее всего, кэшируется в L1 (за исключением того, что в этот момент оно было прервано). Однако для точного ответа необходимо очень хорошо знать микроархитектуру, поскольку вполне возможно, что нынешние процессоры настолько продвинуты, что могут извлекать и декодировать обе ветви одновременно.
Теперь предположим, что у вас есть глобальная переменнаяvolatile int c = 15
и мы меняем ваш код:
if (likely(b == 15))
{
puts("Azaza");
} else {
puts("Bzaza");
}
Когда мы выполняем код и к нему обращаемся впервые, его не будет в кеше, и процессору придется извлечь его из памяти. Это требует нескольких сотен циклов ЦП, и вместо остановки ЦП начинает спекулятивно выполнять код, не зная значенияb
. В этом случае ключевое слово «вероятно» указывает, что нам следует выполнить первую ветвь. Обратите внимание, что выполняемые инструкции в это время не видны внешнему миру. Современные процессоры x86 могут спекулятивно выполнять до 400 микроопераций и фиксировать результат только в том случае, если прогноз оказывается верным.
Поэтому, чтобы ответить на ваш вопрос, я бы поставилlikely/unlikely
ключевое слово вокругa > b
.