GNU/Linux >> Linux の 問題 >  >> Linux

Linux カーネルの可能性が高い/可能性が低いマクロはどのように機能し、その利点は何ですか?

それらは、分岐予測がジャンプ命令の「可能性が高い」側を支持するようにする命令を発行するためのコンパイラへのヒントです。これは大きな勝利になる可能性があります。予測が正しければ、ジャンプ命令は基本的に無料であり、ゼロサイクルかかることを意味します。一方、予測が間違っている場合は、プロセッサ パイプラインをフラッシュする必要があり、数サイクルのコストがかかる可能性があることを意味します。ほとんどの場合、予測が正しい限り、これはパフォーマンスに良い傾向があります。

このようなすべてのパフォーマンスの最適化と同様に、広範なプロファイリングを行ってからコードが実際にボトルネックになっていることを確認してから実行する必要があります。これはおそらく、コードがタイトなループで実行されているというミクロの性質を考慮したものです。一般的に、Linux 開発者はかなりの経験を積んでいるので、彼らがそうしただろうと想像します。彼らは gcc のみを対象としているため、移植性についてはあまり気にしません。また、gcc で生成したいアセンブリについて非常に近い考えを持っています。


逆コンパイルして、GCC 4.8 で何ができるか見てみましょう

__builtin_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 戻る。

__builtin_expect

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 main() {
    int i = !time(NULL);
    if (i)
        goto printf;
puts:
    puts("a");
    return 0;
printf:
    printf("%d\n", i);
    goto puts;
}

この最適化は -O0 では行われませんでした .

しかし、__builtin_expect でより高速に実行される例を書いて頑張ってください。 最近の CPU は非常にスマートです。私の単純な試みはここにあります。

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

C++20 はこれらの C++ ビルトインを標準化しました:if-else ステートメントで C++20 の like/unlikely 属性を使用する方法 それらはおそらく (しゃれ!) 同じことをします.


これらは、分岐がどの方向に進むかについてコンパイラーにヒントを与えるマクロです。マクロは、利用可能な場合、GCC 固有の拡張機能に展開されます。

GCC はこれらを使用して分岐予測を最適化します。たとえば、次のようなものがあるとします

if (unlikely(x)) {
  dosomething();
}

return x;

次に、このコードを次のように再構築できます。

if (!x) {
  return x;
}

dosomething();
return x;

これの利点は、プロセッサが最初に分岐するときに、かなりのオーバーヘッドが発生することです。これは、プロセッサが投機的にコードを読み込んで実行していた可能性があるためです。分岐を取ると判断したら、それを無効にし、分岐先から開始する必要があります。

最新のプロセッサのほとんどは、ある種の分岐予測を備えていますが、それは以前に分岐を通過したことがあり、分岐がまだ分岐予測キャッシュにある場合にのみ役立ちます。

これらのシナリオでコンパイラとプロセッサが使用できる戦略は他にも多数あります。ウィキペディアで分岐予測子の仕組みの詳細を確認できます:http://en.wikipedia.org/wiki/Branch_predictor


Linux
  1. Linuxカーネルモジュールのmodule_initとinit_moduleの違いは何ですか?

  2. Linux カーネルの copy_from_user は内部でどのように機能しますか?

  3. 独自の Linux カーネルをコンパイルする利点は何ですか?

  1. 新しいハードウェア サポートはどのように Linux カーネルに追加されますか?

  2. カーネルでのありそうな呼び出しとありそうもない呼び出しの違いは何ですか?

  3. カーネルパラメータ acpi_osi=linux と acpi_backlight=vendor は何をしますか?

  1. Makefileとは何ですか?どのように機能しますか?

  2. LinuxでのChownコマンドとは何ですか?その使用方法

  3. ローリングリリースLinuxとは何ですか?それを使用することの本当の利点は何ですか