Apa perbedaan antara panggilan yang mungkin dan yang tidak mungkin di Kernel?


11

Apa antara panggilan yang mungkin dan tidak mungkin di Kernel. Saat mencari melalui sumber kernel saya menemukan pernyataan ini.

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

Bisakah seseorang menjelaskannya?


Ini benar-benar pertanyaan pemrograman, lebih cocok untuk Stack OVerflow .
Gilles 'SANGAT berhenti menjadi jahat'

Jawaban:


14

Mereka adalah petunjuk kompiler untuk GCC. Mereka digunakan dalam kondisi untuk memberi tahu kompiler jika cabang kemungkinan diambil atau tidak. Ini dapat membantu kompiler meletakkan kode sedemikian rupa sehingga optimal untuk hasil yang paling sering.

Mereka digunakan seperti ini:

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();
}

Ini harus digunakan dengan sangat hati-hati (yaitu berdasarkan hasil profil cabang yang sebenarnya). Petunjuk yang salah dapat menurunkan kinerja (jelas).

Beberapa contoh bagaimana kode dapat dioptimalkan mudah ditemukan dengan mencari GCC __builtin_expect. Blog ini memposting optimasi gcc: __builtin_expect misalnya rincian pembongkaran dengannya.

Jenis optimasi yang dapat dilakukan sangat spesifik untuk prosesor. Gagasan umum adalah bahwa seringkali, prosesor akan menjalankan kode lebih cepat jika tidak bercabang / melompati semua tempat. Semakin linear, dan semakin mudah diprediksi cabang, semakin cepat akan berjalan. (Ini terutama berlaku untuk prosesor dengan pipa dalam misalnya).

Jadi kompiler akan memancarkan kode sedemikian rupa sehingga cabang yang paling mungkin tidak akan melibatkan lompatan jika itu yang lebih disukai CPU target, misalnya.


Apa yang dimaksud dengan unicorn ? Apakah ini istilah teknis atau hanya pengisi?
Sen

Saya menghapus unicorn untuk menghindari kebingungan.
Mat

Bisakah Anda menguraikan kompiler akan mencoba dan membuat tata letak kode yang optimal untuk kasus ini ? Saya ingin tahu bagaimana cara melakukannya.
Sen

menambahkan sedikit informasi tentang itu. tidak ada cara umum untuk mengoptimalkan kode, semuanya tergantung pada prosesor.
Mat

2

Mari kita dekompilasi untuk melihat apa yang dilakukan GCC 4.8 dengan itu

Tanpa harapan

#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;
}

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 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

Urutan instruksi dalam memori tidak berubah: pertama printfdan kemudian putsdan retqkembali.

Dengan harapan

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 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>

The printf(dikompilasi untuk __printf_chk) dipindahkan ke akhir fungsi, setelah putsdan kembali untuk meningkatkan prediksi cabang seperti yang disebutkan oleh jawaban lainnya.

Jadi pada dasarnya sama dengan:

int i = !time(NULL);
if (i)
    goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;

Optimasi ini tidak dilakukan dengan -O0.

Tapi semoga berhasil menulis contoh yang berjalan lebih cepat dengan __builtin_expecttanpa, 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: /programming/51797959/how-to-use-c20s- maybe-unihood-attribute-in-if-else-statement Mereka kemungkinan akan (a pun!) lakukan hal yang sama.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.