カーネルの呼び出しの可能性と可能性の違いは何ですか?


11

カーネルでの可能性の高い呼び出しと可能性の低い呼び出しの間には何がありますか?カーネルソースを検索したところ、これらのステートメントが見つかりました。

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

誰かがそれに光を当てることができますか?


これは実際にはプログラミングの質問であり、Stack OVerflowに適しています。
Gilles「SO-邪悪なことをやめなさい」

回答:


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は、それを使用した逆アセンブリの詳細を示しています。

実行できる最適化の種類は、プロセッサによって異なります。一般的な考え方は、多くの場合、プロセッサがすべての場所で分岐/ジャンプしないと、コードがより速く実行されるということです。線形性が高く、分岐が予測可能であるほど、実行が高速になります。(これは、たとえば、深いパイプラインを備えたプロセッサに特に当てはまります。)

そのため、たとえば、ターゲットCPUが優先する場合、コンパイラーは、最も可能性の高い分岐がジャンプを含まないようにコードを発行します。


ユニコーンとはどういう意味ですか?それは専門用語ですか、それとも単なるフィラーですか?
Sen

混乱を避けるためにユニコーンを外しました。
マット

あなたは上で詳しく説明してもらえしようとすると、ケースのコードレイアウト最適を行いますコンパイラは?それがどのように行われるのか知りたいのですが。
Sen

それについて少し情報を追加しました。コードを最適化する一般的な方法はなく、すべてプロセッサに依存しています。
マット

2

逆コンパイルして、GCC 4.8が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、その後putsretqリターン。

期待して

次に置き換えます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_expectCPUは、使用しない場合よりも速く実行される例を書くのに幸運です。当時のCPUは本当に賢いです。私の素朴な試みはここにあります

C ++ 20 [[likely]]および[[unlikely]]

C ++ 20はこれらのC ++組み込みを標準化しました:https : //stackoverflow.com/questions/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement彼らはおそらくしゃれ!)同じことをします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.