內核的likely和unlikely

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章