1. 概念
指令週期是指執行一條指令所需要的時間,一般由若干個機器週期組成,是從取指令、分析指令到指令執行完所需的全部。
預取指令具體方法就是在不命中時,當數據從主存儲器中取出送往CPU的同時,把主存儲器相鄰幾個單元中的數據(稱爲一個數據塊)都取出來送入Cache中。預取指令可以更好的利用 cpu資源。簡單說就是從內存取指令很慢, cpu要等待這個過程。如果能提前預測可能執行的指令,就提前從內存把指令讀到 cache, 由於 cache的訪問速度較內存快,cpu要執行時就不用等很長時間了。
具體來說,CPU預取指,一般都輸順序的,如果頻繁的發生跳轉,CPU的流水線操作就會被打斷,需要重新取指,而如果對那些發生頻率更高的事件,我們可以通過likely告訴編譯器,讓其代碼緊跟在判斷指令後面,會減少跳轉,提高預取指操作的命中率。
如果開發人員可以告訴編譯器,哪個分支更有可能發生(likely) 或者 非常不可能發生(unlikely), 可以幫助編譯器進行代碼編譯。
在linux內核中likely和unlikely有兩種可選,是通過下面這個決定的。
#if defined(CONFIG_TRACE_BRANCH_PROFILING) \
&& !defined(DISABLE_BRANCH_PROFILING) && !defined(__CHECKER__)
void ftrace_likely_update(struct ftrace_branch_data *f, int val, int expect);
#define likely_notrace(x) __builtin_expect(!!(x), 1)
#define unlikely_notrace(x) __builtin_expect(!!(x), 0)
#define __branch_check__(x, expect) ({ \
int ______r; \
static struct ftrace_branch_data \
__attribute__((__aligned__(4))) \
__attribute__((section("_ftrace_annotated_branch"))) \
______f = { \
.func = __func__, \
.file = __FILE__, \
.line = __LINE__, \
}; \
______r = likely_notrace(x); \
ftrace_likely_update(&______f, ______r, expect); \
______r; \
})
/*
* Using __builtin_constant_p(x) to ignore cases where the return
* value is always the same. This idea is taken from a similar patch
* written by Daniel Walker.
*/
# ifndef likely
# define likely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 1))
# endif
# ifndef unlikely
# define unlikely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 0))
# endif
#ifdef CONFIG_PROFILE_ALL_BRANCHES
/*
* "Define 'is'", Bill Clinton
* "Define 'if'", Steven Rostedt
*/
#define if(cond, ...) __trace_if( (cond , ## __VA_ARGS__) )
#define __trace_if(cond) \
if (__builtin_constant_p(!!(cond)) ? !!(cond) : \
({ \
int ______r; \
static struct ftrace_branch_data \
__attribute__((__aligned__(4))) \
__attribute__((section("_ftrace_branch"))) \
______f = { \
.func = __func__, \
.file = __FILE__, \
.line = __LINE__, \
}; \
______r = !!(cond); \
______f.miss_hit[______r]++; \
______r; \
}))
#endif /* CONFIG_PROFILE_ALL_BRANCHES */
#else
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
#endif
#if defined(CONFIG_TRACE_BRANCH_PROFILING) \
&& !defined(DISABLE_BRANCH_PROFILING) && !defined(__CHECKER__)
這個三個值的檢查,確認likely和unlikely是否需要使用branch tracer跟蹤。branch tracer是ftrace的一種trace功能。主要用於跟蹤likely預測的正確率。爲了實現這個功能,branch tracer重新定義了likely和unlikely。
CONFIG_TRACE_BRANCH_PROFILING宏在配置內核時開啓。DISABLE_BRANCH_PROFILING宏只在低級代碼關閉基於每個文件的分支跟蹤。!defined(__CHECKER__)說明在未使用Sparse工具時有效。
struct ftrace_branch_data結構體用於記錄ftrace branch的trace記錄。
likely_notrace和unlikely_notrace宏使用__builtin_expect函數,__builtin_expect告訴編譯器程序設計者期望的比較結果,以便編譯器對代碼進行優化,改變彙編代碼中的判斷跳轉語句。
__branch_check__宏,記錄當前trace點,並利用ftrace_likely_update記錄likely判斷的正確性,並將結果保存在ring buffer中,之後用戶可以通過ftrace的debugfs接口讀取分支預測的相關信息。從而優調整代碼,優化性能。
重定義likely和unlikely宏裏面用到了GCC的build-in函數__builtin_constant_p判斷一個表達式在編譯時是否爲常量。當是常量時,直接返回likely和unlikely表達式的值,沒必要做預測的記錄。當表達式爲非常數時,使用宏__branch_check__檢查分支並記錄likely判斷的預測信息。
如果設置了CONFIG_PROFILE_ALL_BRANCHES宏,將重定義if()爲__trace_if。__trace_if檢查if的所有分支,並記錄分支的跟蹤信息。
可見,上面兩種對開發人員都可以提示編譯器進行優化。而使用了調試接口的則可以對那些if()和else()相差不大的情況下,通過調試接口先調試知道那種可能性大後,再調整軟件。
具體的代碼,參考官方給的文檔,搜索__builtin_expect
https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Other-Builtins.html#Other-Builtins