ความแตกต่างระหว่างการโทรที่เป็นไปได้และไม่น่าเป็นไปได้ในเคอร์เนลคืออะไร?


11

การโทรที่เป็นไปได้และไม่น่าเป็นไปได้ใน Kernel คืออะไร ในขณะที่ค้นหาผ่านแหล่งเคอร์เนลฉันพบคำสั่งเหล่านี้

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

มีใครบางคนส่องแสงเข้าไปบ้างไหม?


นี้เป็นจริงคำถามการเขียนโปรแกรมที่เหมาะสมที่ดีกว่าสำหรับกองมากเกิน
Gilles 'หยุดความชั่วร้าย'

คำตอบ:


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

ฉันลบยูนิคอร์นออกเพื่อหลีกเลี่ยงความสับสน
Mat

คุณช่วยอธิบายอย่างละเอียดเกี่ยวกับคอมไพเลอร์จะพยายามและทำให้รูปแบบรหัสที่ดีที่สุดสำหรับกรณี ? ฉันอยากจะรู้ว่ามันทำอย่างไร
Sen

เพิ่มข้อมูลเล็กน้อยเกี่ยวกับเรื่องนั้น ไม่มีวิธีทั่วไปในการปรับรหัสให้ดีขึ้นอยู่กับตัวประมวลผลทั้งหมด
Mat

2

ลองถอดรหัสเพื่อดูว่า 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แล้วputsและretqกลับ

พร้อมคาดหวัง

ตอนนี้แทนที่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_expectกว่าโดยไม่ต้องใช้ CPU ในวันนั้น ความพยายามที่ไร้เดียงสาของฉันอยู่ที่นี่

C ++ 20 [[likely]]และ[[unlikely]]

C ++ 20 ได้สร้างมาตรฐานในตัว C ++ เหล่านั้น: /programming/51797959/how-to-use-c20s- ไม่น่าจะเป็นไปไม่ได้ที่จะใช้ -in-if-else-statement ปุน!) ทำสิ่งเดียวกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.