В чем разница между вероятными и маловероятными вызовами в ядре?


11

Что такое между вероятными и маловероятными вызовами в ядре. При поиске в ядре я нашел эти утверждения.

# define likely(x)      __builtin_expect(!!(x), 1)
# define unlikely(x)    __builtin_expect(!!(x), 0)

Может ли кто-то пролить свет на это?


Это действительно вопрос программирования, лучше подходящий для Stack OVerflow .
Жиль "ТАК - перестань быть злым"

Ответы:


14

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

Они используются так:

if (likely(some_condition)) {
  // the compiler will try and make the code layout optimal for the case
  // where some_condition is true, i.e. where this block is run
  most_likely_action();
} else {
  // this block is less frequently used
  corner_case();
}

Его следует использовать с большой осторожностью (т.е. на основе фактических результатов профилирования веток). Неправильный намек может ухудшить производительность (очевидно).

Некоторые примеры того, как код может быть оптимизирован, легко найти с помощью поиска GCC __builtin_expect. Этот пост gcc оптимизация: __builtin_expect, например, детализирует разборку с ним.

Тип оптимизации, которая может быть сделана, очень зависит от процессора. Общая идея заключается в том, что часто процессоры будут выполнять код быстрее, если он не будет переходить / перескочить повсюду. Чем он более линейный и чем более предсказуемы ветви, тем быстрее он будет работать. (Это особенно верно для процессоров с глубокими конвейерами, например.)

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


Что подразумевается под единорогами ? Это технический термин или просто наполнитель?
Сен

Я убрал единорогов, чтобы избежать путаницы.
Мат

Не могли бы вы рассказать о том, как компилятор попытается сделать макет кода оптимальным для данного случая ? Я хотел бы знать, как это происходит.
Сен

добавил немного информации об этом. общего способа оптимизации кода не существует, все зависит от процессора.
Мат

2

Давайте декомпилируем, чтобы увидеть, что GCC 4.8 делает с ним

Без ожидания

#include "stdio.h"
#include "time.h"

int main() {
    /* Use time to prevent it from being optimized away. */
    int i = !time(NULL);
    if (i)
        printf("%d\n", 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    $0x8,%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 14                   jne    24 <main+0x24>
  10:       ba 01 00 00 00          mov    $0x1,%edx
  15:       be 00 00 00 00          mov    $0x0,%esi
                    16: R_X86_64_32 .rodata.str1.1
  1a:       bf 01 00 00 00          mov    $0x1,%edi
  1f:       e8 00 00 00 00          callq  24 <main+0x24>
                    20: R_X86_64_PC32       __printf_chk-0x4
  24:       bf 00 00 00 00          mov    $0x0,%edi
                    25: R_X86_64_32 .rodata.str1.1+0x4
  29:       e8 00 00 00 00          callq  2e <main+0x2e>
                    2a: R_X86_64_PC32       puts-0x4
  2e:       31 c0                   xor    %eax,%eax
  30:       48 83 c4 08             add    $0x8,%rsp
  34:       c3                      retq

Порядок инструкций в памяти не изменился: сначала и, printfа затем putsи retqвозврат.

С ожиданием

Теперь замените if (i)на:

if (__builtin_expect(i, 0))

и мы получаем:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%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 11                   je     21 <main+0x21>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1+0x4
  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    $0x8,%rsp
  20:       c3                      retq
  21:       ba 01 00 00 00          mov    $0x1,%edx
  26:       be 00 00 00 00          mov    $0x0,%esi
                    27: R_X86_64_32 .rodata.str1.1
  2b:       bf 01 00 00 00          mov    $0x1,%edi
  30:       e8 00 00 00 00          callq  35 <main+0x35>
                    31: R_X86_64_PC32       __printf_chk-0x4
  35:       eb d9                   jmp    10 <main+0x10>

printf(Компилируется __printf_chk) был перенесен в самом конце функции, после того, как putsи возвращение , чтобы улучшить предсказание ветвлений , как упоминалось другими ответами.

Так что это в основном так же, как:

int i = !time(NULL);
if (i)
    goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;

Эта оптимизация не была сделана с -O0.

Но удачи в написании примера, который работает быстрее, __builtin_expectчем без, в те дни процессоры действительно умны . Мои наивные попытки здесь .

С ++ 20 [[likely]]и[[unlikely]]

C ++ 20 стандартизировал эти встроенные модули C ++: /programming/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Они, скорее всего, каламбур!) сделать то же самое.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.