Каково различие между вероятными и маловероятными вызовами в Ядре?

Я просто хочу добавить, что существуют диспетчеры пакетов, которые компилируют пакеты из источника и обрабатывают все зависимости от пакета, флаги, и т.д.

В системах BSD это ports: Использование набора портов

В Debian, apt-get диспетчер пакетов может установить из источника также: ПРАКТИЧЕСКОЕ РУКОВОДСТВО APT: Работа с исходными пакетами (То же идет для Ubuntu, Linux Mint и всего остального на основе Debian),

Хинду распределение использует portage диспетчер пакетов, который компилирует целую систему из источника только: Введение Перевозки.

Slackware может скомпилировать пакеты, но я не знаю, существует ли какой-либо диспетчер пакетов для этого там..=)

Так или иначе можно всегда компилировать пакеты вручную как Sandy, упомянул выше =) Также должно быть возможно использовать apt-get или portage диспетчеры пакетов в любом другом дистрибутиве...

11
19.04.2011, 10:12
2 ответа

Они - подсказки компилятора для 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, например, детализирует дизассемблирование с ним.

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

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

14
27.01.2020, 19:58
  • 1
    Что предназначено единорогами? Действительно ли это - технический термин или просто заполнитель? –  Sen 19.04.2011, 11:03
  • 2
    я удалил единорогов для предотвращения беспорядка. –  Mat 19.04.2011, 11:05
  • 3
    Вы могли уточнить компилятор, попытается сделать размещение кода оптимальным для случая? Я хотел бы знать, как это делает это. –  Sen 19.04.2011, 11:08
  • 4
    добавил немного информации об этом. нет никакого общего способа оптимизировать код, это - весь самый зависимый процессора. –  Mat 19.04.2011, 11:19

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

Без expect

#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 return.

С ожиданием

Теперь замените 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 [[вероятно]] и [[маловероятно]]

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

2
27.01.2020, 19:58

Теги

Похожие вопросы