В чем преимущество встроенных в GCC ожиданий, если еще заявления?
я наткнулся на #define в котором они использовать __builtin_expect.
документация говорит:
встроенные функции:
long __builtin_expect (long exp, long c)
вы можете использовать
__builtin_expectчтобы предоставить компилятору ветку
прогнозная информация. В общем, вы должны предпочесть использовать фактические
профиль обратной связи для этого (-fprofile-arcs), как программисты
как известно, плохо предсказывает, как их программы На самом деле работают.
Тем не менее, есть приложения в которой эти данные трудно собрать.
возвращаемое значение-это значение
exp, который должен быть интегральным
выражение. Семантика встроенного заключается в том, что ожидается, что
exp == c. Например:
if (__builtin_expect (x, 0))
foo ();
означало бы, что мы не ожидаем вызова
foo, так как мы ожидаемxк нулю.
так почему бы не использовать напрямую:
if (x)
foo ();
вместо сложного синтаксиса с __builtin_expect?
5 ответов:
представьте себе ассемблерный код, который будет сгенерирован из:
if (__builtin_expect(x, 0)) { foo(); ... } else { bar(); ... }Я думаю, это должно быть что-то вроде:
cmp $x, 0 jne _foo _bar: call bar ... jmp after_if _foo: call foo ... after_if:вы можете видеть, что инструкции расположены в таком порядке, что
barслучае передfoocase (в отличие от кода C). Это может использовать конвейер ЦП лучше, так как прыжок разбивает уже полученные инструкции.перед выполнением прыжка, инструкции под ним (
barслучае) подтолкнули к трубопроводу. Так какfooслучай маловероятен, прыжки тоже маловероятны, следовательно, обрушение трубопровода маловероятно.
идея
__builtin_expectдолжен сообщить компилятору, что вы обычно обнаружите, что выражение вычисляется до c, так что компилятор может оптимизировать для этого случая.Я думаю, что кто-то думал, что они были умны и что они ускоряли вещи, делая это.
к сожалению, если ситуация не будет очень хорошо понял (вполне вероятно, что они не сделали ничего подобного), это вполне могло сделать вещи хуже. Документация даже говорит:
В общем, вы должны предпочесть использовать фактическую обратную связь профиля для этого (
-fprofile-arcs), поскольку программисты, как известно, плохо предсказывают, как их программы На самом деле работают. Однако, существуют приложения, в которых эти данные трудно собрать.В общем, вы не должны использовать
__builtin_expectЕсли:
- у вас есть очень реальная проблема производительности
- вы уже оптимизировали алгоритмы в системе соответственно
- у вас есть данные о производительности для резервного копирования вашего утверждения, что в конкретном случае является наиболее вероятным
давайте декомпилируем, чтобы увидеть, что GCC 4.8 делает с ним
Благовест упомянул инверсию ветвей для улучшения трубопровода, но действительно ли это делают текущие компиляторы? Давайте выясним!
без
__builtin_expect#include "stdio.h" #include "time.h" int main() { /* Use time to prevent it from being optimized away. */ int i = !time(NULL); if (i) puts("a"); return 0; }компилировать и декомпилировать с помощью GCC 4.8.2 x86_64 Linux:
gcc -c -O3 -std=gnu11 main.c objdump -dr main.oвыход:
0000000000000000 <main>: 0: 48 83 ec 08 sub x8,%rsp 4: 31 ff xor %edi,%edi 6: e8 00 00 00 00 callq b <main+0xb> 7: R_X86_64_PC32 time-0x4 b: 48 85 c0 test %rax,%rax e: 75 0a jne 1a <main+0x1a> 10: bf 00 00 00 00 mov x0,%edi 11: R_X86_64_32 .rodata.str1.1 15: e8 00 00 00 00 callq 1a <main+0x1a> 16: R_X86_64_PC32 puts-0x4 1a: 31 c0 xor %eax,%eax 1c: 48 83 c4 08 add x8,%rsp 20: c3 retqпорядок команд в памяти не изменился: сначала
putsа тоretqвозвращаться.С
__builtin_expectзаменить
if (i)С:if (__builtin_expect(i, 0))и мы получаем:
0000000000000000 <main>: 0: 48 83 ec 08 sub x8,%rsp 4: 31 ff xor %edi,%edi 6: e8 00 00 00 00 callq b <main+0xb> 7: R_X86_64_PC32 time-0x4 b: 48 85 c0 test %rax,%rax e: 74 07 je 17 <main+0x17> 10: 31 c0 xor %eax,%eax 12: 48 83 c4 08 add x8,%rsp 16: c3 retq 17: bf 00 00 00 00 mov x0,%edi 18: R_X86_64_32 .rodata.str1.1 1c: e8 00 00 00 00 callq 21 <main+0x21> 1d: R_X86_64_PC32 puts-0x4 21: eb ed jmp 10 <main+0x10>The
putsбыл перемещен в самый конец функции,retqвозвращение!новый код в основном такой же, как:
int i = !time(NULL); if (i) goto puts; ret: return 0; puts: puts("a"); goto ret;эта оптимизация не была выполнена с
-O0.но удачи в написании примера, который работает быстрее с
__builtin_expectчем без, процессоры действительно умны в те дни. Мои наивные попытки здесь.
Ну, как говорится в описании, первая версия добавляет прогностический элемент в конструкцию, сообщая компилятору, что
x == 0ветвь является более вероятной - то есть это ветвь, которая будет чаще использоваться вашей программой.имея это в виду, компилятор может оптимизировать условное так, чтобы оно требовало наименьшего количества работы, когда выполняется ожидаемое условие, за счет, возможно, необходимости выполнять больше работы в случае неожиданного состояние.
взгляните на то, как реализуются условные выражения на этапе компиляции, а также в результирующей сборке, чтобы увидеть, как одна ветвь может работать меньше, чем другая.
однако я ожидал бы, что эта оптимизация будет иметь заметный эффект, если рассматриваемое условие является частью плотного внутреннего цикла, который называется много, так как разница в полученном коде относительно невелика. И если вы оптимизируете его неправильно, вы вполне можете снизить свою производительность.
Я не вижу ни одного из ответов на вопрос, который, как мне кажется, вы задавали, перефразировал:
есть ли более переносимый способ намекать на предсказание ветвей компилятору.
название вашего вопроса заставило меня подумать об этом так:
if ( !x ) {} else foo();если компилятор предполагает, что "true" более вероятно, он может оптимизировать для не вызова
foo().проблема здесь только в том, что вы, в общем-то, не знаете то, что компилятор будет предполагать-поэтому любой код, который использует этот вид техники, должен быть тщательно измерен (и, возможно, контролироваться с течением времени, если контекст изменится).
Comments