В чем преимущество встроенных в 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?

674   5  

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 случае перед foo case (в отличие от кода 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

    Ничего не найдено.