Что такое между вероятными и маловероятными вызовами в ядре. При поиске в ядре я нашел эти утверждения.
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
Может ли кто-то пролить свет на это?
Что такое между вероятными и маловероятными вызовами в ядре. При поиске в ядре я нашел эти утверждения.
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
Может ли кто-то пролить свет на это?
Ответы:
Это подсказки компилятора для 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, например, детализирует разборку с ним.
Тип оптимизации, которая может быть сделана, очень зависит от процессора. Общая идея заключается в том, что часто процессоры будут выполнять код быстрее, если он не будет переходить / перескочить повсюду. Чем он более линейный и чем более предсказуемы ветви, тем быстрее он будет работать. (Это особенно верно для процессоров с глубокими конвейерами, например.)
Таким образом, компилятор выдаст код так, что наиболее вероятная ветвь не будет включать скачок, если, например, это то, что предпочитает целевой процессор.
Давайте декомпилируем, чтобы увидеть, что 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 Они, скорее всего, каламбур!) сделать то же самое.