Яка різниця між ймовірними та малоймовірними дзвінками в Kernel?


11

Яка між ймовірними та малоймовірними дзвінками в Kernel. Під час пошуку через джерело ядра я знайшов ці твердження.

# 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 optimization: __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ніж без нього, процесори справді розумні в ті дні . Мої наївні спроби тут .

C ++ 20 [[likely]]і[[unlikely]]

C ++ 20 стандартизував ці вбудовані файли C ++: /programming/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Вони, ймовірно, (a каламбур!) зробіть те саме.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.