GNU/Linux >> Belajar Linux >  >> Linux

Apa keuntungan dari __builtin_expect GCC dalam pernyataan if else?

Bayangkan kode rakitan yang akan dihasilkan dari:

if (__builtin_expect(x, 0)) {
    foo();
    ...
} else {
    bar();
    ...
}

Saya kira itu harus seperti:

  cmp   $x, 0
  jne   _foo
_bar:
  call  bar
  ...
  jmp   after_if
_foo:
  call  foo
  ...
after_if:

Anda dapat melihat bahwa instruksi disusun sedemikian rupa sehingga bar huruf besar mendahului foo kasus (sebagai lawan dari kode C). Ini dapat memanfaatkan pipa CPU dengan lebih baik, karena lompatan meronta-ronta instruksi yang sudah diambil.

Sebelum lompatan dijalankan, instruksi di bawahnya (kode bar kasus) didorong ke pipa. Sejak foo kasusnya tidak mungkin, melompat juga tidak mungkin, karenanya meronta-ronta jalur pipa tidak mungkin terjadi.


Mari kita dekompilasi untuk melihat apa yang dilakukan GCC 4.8 dengannya

Blagovest menyebutkan inversi cabang untuk meningkatkan saluran pipa, tetapi apakah kompiler saat ini benar-benar melakukannya? Ayo cari tahu!

Tanpa __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)
        puts("a");
    return 0;
}

Kompilasi dan dekompilasi dengan GCC 4.8.2 x86_64 Linux:

gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o

Keluaran:

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 0a                   jne    1a <main+0x1a>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1
  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

Urutan instruksi dalam memori tidak berubah:pertama puts lalu retq kembali.

Dengan __builtin_expect

Sekarang ganti if (i) dengan:

if (__builtin_expect(i, 0))

dan kami mendapatkan:

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 07                   je     17 <main+0x17>
  10:       31 c0                   xor    %eax,%eax
  12:       48 83 c4 08             add    $0x8,%rsp
  16:       c3                      retq
  17:       bf 00 00 00 00          mov    $0x0,%edi
                    18: R_X86_64_32 .rodata.str1.1
  1c:       e8 00 00 00 00          callq  21 <main+0x21>
                    1d: R_X86_64_PC32       puts-0x4
  21:       eb ed                   jmp    10 <main+0x10>

puts dipindahkan ke bagian paling akhir dari fungsi, retq kembali!

Kode baru ini pada dasarnya sama dengan:

int i = !time(NULL);
if (i)
    goto puts;
ret:
return 0;
puts:
puts("a");
goto ret;

Pengoptimalan ini tidak dilakukan dengan -O0 .

Tapi semoga berhasil menulis contoh yang berjalan lebih cepat dengan __builtin_expect daripada tanpa, CPU benar-benar pintar saat itu. Upaya naif saya ada di sini.

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

C++20 telah menstandarkan C++ built-in tersebut:Cara menggunakan atribut C++20 kemungkinan/tidak mungkin dalam pernyataan if-else Kemungkinan besar (pun!) akan melakukan hal yang sama.


Gagasan __builtin_expect adalah memberi tahu kompiler bahwa Anda biasanya akan menemukan bahwa ekspresi dievaluasi ke c, sehingga kompiler dapat mengoptimalkan untuk kasus tersebut.

Saya kira seseorang mengira mereka pintar dan mereka mempercepat dengan melakukan ini.

Sayangnya, kecuali situasinya dipahami dengan sangat baik (kemungkinan mereka tidak melakukan hal seperti itu), itu mungkin memperburuk keadaan. Dokumentasi bahkan mengatakan:

Secara umum, Anda sebaiknya memilih untuk menggunakan umpan balik profil aktual untuk ini (-fprofile-arcs ), karena pemrogram terkenal buruk dalam memprediksi kinerja program mereka sebenarnya. Namun, ada aplikasi yang sulit mengumpulkan data ini.

Secara umum, Anda tidak boleh menggunakan __builtin_expect kecuali:

  • Anda memiliki masalah kinerja yang sangat nyata
  • Anda telah mengoptimalkan algoritme dalam sistem dengan tepat
  • Anda memiliki data kinerja untuk mendukung pernyataan Anda bahwa kemungkinan besar kasus tertentu

Linux
  1. Apa trik LD_PRELOAD?

  2. Apa arti dari *nix?

  3. Apa arti dari Peringatan:Menautkan pustaka bersama dengan pustaka statis tidak portabel?

  1. Apa konsep vruntime di CFS

  2. Apa gunanya $# di Bash

  3. Apa itu tautan emas?

  1. Linux vs. Unix:Apa bedanya?

  2. Apa yang Terjadi Pada Ruang Kosong?

  3. Apa itu pengguna debian-+?