การโทรที่เป็นไปได้และไม่น่าเป็นไปได้ใน Kernel คืออะไร ในขณะที่ค้นหาผ่านแหล่งเคอร์เนลฉันพบคำสั่งเหล่านี้
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
มีใครบางคนส่องแสงเข้าไปบ้างไหม?
การโทรที่เป็นไปได้และไม่น่าเป็นไปได้ใน Kernel คืออะไร ในขณะที่ค้นหาผ่านแหล่งเคอร์เนลฉันพบคำสั่งเหล่านี้
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
มีใครบางคนส่องแสงเข้าไปบ้างไหม?
คำตอบ:
นี่เป็นคำแนะนำของคอมไพเลอร์สำหรับ 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 เป้าหมายชอบ
ลองถอดรหัสเพื่อดูว่า 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 ปุน!) ทำสิ่งเดียวกัน