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

11

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

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

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

сен
источник
Это действительно вопрос программирования, лучше подходящий для Stack OVerflow .
Жиль "ТАК - перестань быть злым"
stackoverflow.com/questions/109710/…
Сиро Сантилли 冠状 病毒 审查 六四 事件 法轮功

Ответы:

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 Они, скорее всего, каламбур!) сделать то же самое.

Сиро Сантилли 冠状 病毒 审查 六四 事件 法轮功
источник