内核的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

你可能感兴趣的:(linxu驱动基础)