GCC __builtin_expect与kernel指令序列优化
例題描述
例題描述:通過C語言識別一個int型數據在十進制下是否為回文數字。不能有額外的字符串空間開銷。
如:2156512是回文數,而21565不是回文數。
問題分析:
1. 當這個數字是負數的時候,肯定不是回文數
2. 可以將這個數翻轉,判斷翻轉后是否相同
C語言代碼演示:
/*************************************************************************> File Name: isPalindrome.cpp> Author: ChenXiansen > Mail: 1494089474@qq.com > Created Time: Wed 11 Nov 2020 09:49:43 AM CST************************************************************************/#include <stdio.h>bool isPalindrome(int x, int n) {if (__builtin_expect(!!(x < 0), 0)) return false;int y = 0, z = x;while (x) {y = y * n + x % n;x /= n;}return z == y; }int main() {int n, Cov = 10;scanf("%d", &n);if (isPalindrome(n, Cov)) {printf("Num: %d in Cov: %d is a reverse num!\n", n, Cov);} else {printf("NO! It's not a reverse num!\n");}return 0; }代碼分析
在 isPalindrome函數中,使用了if (__builtin_expect(!!(x < 0), 0)) return false; 這條語句。
下面是對__builtin_expect宏的表述:
GCC提供了__builtin_expect宏,作為編譯分支時候的暗示。用法是__builtin_expect(var, expected_value),也就是說,告訴編譯器var這個變量的值比較可能是什么。在kernel中這個宏被用在likely和unlikely這兩個宏定義中:
#define likely(x) __builtin_expect(!!(x), 1) //x很可能成立 #define unlikely(x) __builtin_expect(!!(x), 0) //x很可能不成立從現代的處理器架構說起。相信大家都知道流水線技術,就是CPU可以在統一個時鐘周期內同時執行多條指令,當前指令尚未執行完畢,實際上就已經開始處理后面的指令了。然而當處理器遇到分支的時候,就無法判斷即將執行的是哪個分支,流水線優化就受到了限制。
后來,隨著處理器技術的發展,處理器開始直接預取分支后面的指令,如果發現分支預判錯誤,則拋棄之前的執行結果,重新轉入正確的分支繼續執行。 更加現代的處理器甚至能夠預取更多后面的指令,對于不依賴之前執行結果的指令都可以按照一定的規則預先執行得到結果。
匯編代碼
1. 初始代碼匯編
接下來在終端運行gcc -S isPalindrome.cpp,查看匯編代碼中的isPalindrome()函數:
.LFB0:.cfi_startprocendbr64pushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6movl %edi, -20(%rbp)movl %esi, -24(%rbp)movl -20(%rbp), %eaxshrl $31, %eaxmovzbl %al, %eaxtestq %rax, %raxje .L2movl $0, %eaxjmp .L3 .L2:movl $0, -8(%rbp)movl -20(%rbp), %eaxmovl %eax, -4(%rbp) .L5:cmpl $0, -20(%rbp)je .L4movl -8(%rbp), %eaximull -24(%rbp), %eaxmovl %eax, %ecxmovl -20(%rbp), %eaxcltdidivl -24(%rbp)movl %edx, %eaxaddl %ecx, %eaxmovl %eax, -8(%rbp)movl -20(%rbp), %eaxcltdidivl -24(%rbp)movl %eax, -20(%rbp)jmp .L5 .L4:movl -4(%rbp), %eaxcmpl -8(%rbp), %eaxsete %al .L3:popq %rbp.cfi_def_cfa 7, 8ret.cfi_endproc .LFE0:.size _Z12isPalindromeii, .-_Z12isPalindromeii.section .rodata對.LFB0段的釋義:
movl %esi, -24(%rbp)
將這兩個參數壓入函數棧。
修改程序后匯編:
現在將
if (__builtin_expect(!!(x < 0), 0)) return false;這部分C代碼轉換成
if (x < 0) return false;觀察執行預處理-編譯-匯編過程后,查看匯編代碼中的isPalindrome()函數。(由于.L2之后的部分完全相同,因此沒有在博客中顯示)
.LFB0:.cfi_startprocendbr64pushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6movl %edi, -20(%rbp)movl %esi, -24(%rbp)cmpl $0, -20(%rbp)jns .L2movl $0, %eaxjmp .L3可以看到:將函數參數全部壓棧后,進行的分支判斷:
movl %edi, -20(%rbp)movl %esi, -24(%rbp)cmpl $0, -20(%rbp)jns .L2movl $0, %eaxjmp .L3cmpl $0, -20(%rbp):如果 bool isPalindrome(int x, int n)函數中 參數x的值不滿足 x < 0,則執行.L2之后的匯編函數,反之直接return 0.
參考資料:
https://zhuanlan.zhihu.com/p/27339191
http://deltamaster.is-programmer.com/posts/37285.html
附錄:完整匯編程序代碼:
.file "isPalindrome.cpp".text.globl _Z12isPalindromeii.type _Z12isPalindromeii, @function _Z12isPalindromeii: .LFB0:.cfi_startprocendbr64pushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6movl %edi, -20(%rbp)movl %esi, -24(%rbp)movl -20(%rbp), %eaxshrl $31, %eaxmovzbl %al, %eaxtestq %rax, %raxje .L2movl $0, %eaxjmp .L3 .L2:movl $0, -8(%rbp)movl -20(%rbp), %eaxmovl %eax, -4(%rbp) .L5:cmpl $0, -20(%rbp)je .L4movl -8(%rbp), %eaximull -24(%rbp), %eaxmovl %eax, %ecxmovl -20(%rbp), %eaxcltdidivl -24(%rbp)movl %edx, %eaxaddl %ecx, %eaxmovl %eax, -8(%rbp)movl -20(%rbp), %eaxcltdidivl -24(%rbp)movl %eax, -20(%rbp)jmp .L5 .L4:movl -4(%rbp), %eaxcmpl -8(%rbp), %eaxsete %al .L3:popq %rbp.cfi_def_cfa 7, 8ret.cfi_endproc .LFE0:.size _Z12isPalindromeii, .-_Z12isPalindromeii.section .rodata .LC0:.string "%d".align 8 .LC1:.string "Num: %d in Cov: %d is a reverse num!\n" .LC2:.string "NO! It's not a reverse num!".text.globl main.type main, @function main: .LFB1:.cfi_startprocendbr64pushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6subq $16, %rspmovq %fs:40, %raxmovq %rax, -8(%rbp)xorl %eax, %eaxmovl $10, -12(%rbp)leaq -16(%rbp), %raxmovq %rax, %rsileaq .LC0(%rip), %rdimovl $0, %eaxcall __isoc99_scanf@PLTmovl -16(%rbp), %eaxmovl -12(%rbp), %edxmovl %edx, %esimovl %eax, %edicall _Z12isPalindromeiitestb %al, %alje .L7movl -16(%rbp), %eaxmovl -12(%rbp), %edxmovl %eax, %esileaq .LC1(%rip), %rdimovl $0, %eaxcall printf@PLTjmp .L8 .L7:leaq .LC2(%rip), %rdicall puts@PLT .L8:movl $0, %eaxmovq -8(%rbp), %rcxxorq %fs:40, %rcxje .L10call __stack_chk_fail@PLT .L10:leave.cfi_def_cfa 7, 8ret.cfi_endproc .LFE1:.size main, .-main.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0".section .note.GNU-stack,"",@progbits.section .note.gnu.property,"a".align 8.long 1f - 0f.long 4f - 1f.long 5 0:.string "GNU" 1:.align 8.long 0xc0000002.long 3f - 2f 2:.long 0x3 3:.align 8 4:總結
以上是生活随笔為你收集整理的GCC __builtin_expect与kernel指令序列优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GCC编译过程以及对应FILE文件表
- 下一篇: 三分查找C语言实现