GNU/Linux >> Belajar Linux >  >> Linux

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

Itu adalah petunjuk penyusun untuk GCC. Mereka digunakan dalam persyaratan untuk memberi tahu kompiler jika sebuah cabang kemungkinan akan diambil atau tidak. Ini dapat membantu kompiler menyusun 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();
}

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

Beberapa contoh bagaimana kode dapat dioptimalkan dengan mudah ditemukan dengan mencari GCC __builtin_expect . Optimalisasi gcc postingan blog ini:__builtin_expect misalnya merinci pembongkaran dengannya.

Jenis pengoptimalan yang dapat dilakukan sangat spesifik untuk prosesor. Ide umumnya adalah bahwa seringkali, prosesor akan menjalankan kode lebih cepat jika tidak bercabang/melompat di semua tempat. Semakin linier, dan semakin dapat diprediksi cabangnya, semakin cepat ia berjalan. (Hal ini terutama berlaku untuk prosesor dengan pipeline yang dalam, misalnya.)

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


Mari kita dekompilasi untuk melihat apa yang dilakukan GCC 4.8 dengannya

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 printf lalu puts dan retq kembali.

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>

printf (dikompilasi ke __printf_chk ) dipindahkan ke akhir fungsi, setelah puts dan pengembalian untuk meningkatkan prediksi cabang seperti yang disebutkan oleh jawaban lain.

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;

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 menstandarisasi C++ built-in tersebut:https://stackoverflow.com/questions/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Mereka kemungkinan akan (a pun!) melakukan hal yang sama.


Linux
  1. Bagaimana cara kerja makro yang mungkin/tidak mungkin dalam kernel Linux dan apa manfaatnya?

  2. Apa perbedaan antara strtok_r dan strtok_s di C?

  3. Apa perbedaan antara fsck dan e2fsck?

  1. Apa perbedaan antara adduser dan useradd?

  2. Apa perbedaan antara panggilan Perpustakaan dan panggilan Sistem di Linux?

  3. Apa perbedaan antara driver kernel dan modul kernel?

  1. Apa perbedaan antara `su -` dan` su --login`?

  2. Apa perbedaan antara partx dan kpartx?

  3. Apa perbedaan antara unlink dan rm?