CPU特征
缓存
程序的执行过程中程序指令及数据会按照以下方向流动:
外部存储 -> 内存 -> L3缓存 -> L2缓存 -> L1缓存 -> 寄存器
在计算机体系结构中这些存储部件有以下特征:
- 每一类型的存储空间从左到右逐级变小
- 每一类型的读写速度从左到右逐级变快
- CPU运行的时候优先从右边的存储空间中获取,若没有则逐级向左查询
所以如果希望程序运行的更快的话,就需要每次读取指令/数据的命中越靠右越好
指令流
一条指令的执行过程至少包括读取指令,解释指令和执行指令,当前计算的设计将这三个阶段设计为可并发执行,例如如下场景:
time1: 读取指令1
time2: 解释指令1 读取指令2
time3: 执行指令1 解释指令2 读取指令3
............................................................................
从以上设计可以看出将指令分成更细的步骤比起串行有更高的效率,前提是提前执行的下一条指令是有效的。
条件分支
在编码中经常会写类似如下的逻辑:
按照理解这段程序汇编伪代码应该时如下形式:
...............
cmpl
jle L1
................. //执行条件为真时逻辑
jmp L2 //跳转至后续逻辑继续执行
L1:
.............. //执行条件为假时逻辑
L2:
...................... //执行后续逻辑
而在打开优化的情况下对应到汇编的指令如下:
实际汇编代码逻辑如下:
.........................
cmpl
jle L1
............
L2:
...........
retq
L1:
.................
jmp L2
可以发现当编译器打开优化功能时程序的执行逻辑对于条件为真的情况相当友好,有以下优势
- 增加了指令jle 后续指令执行的命中率
- 省去了最后的一句jmp语句
当希望编译器对条件为假的情况友好时,可以有以下两种方法
- 将条件语句的控制条件反转
- 不改变控制条件在if语句中使用unlikely提示编译器
最后编译器生成汇编如下:
likely/unlikely 宏
#define __branch_check__(x, expect, is_constant) ({ \
long ______r; \
static struct ftrace_likely_data \
__aligned(4) \
__section("_ftrace_annotated_branch") \
______f = { \
.data.func = __func__, \
.data.file = __FILE__, \
.data.line = __LINE__, \
}; \
______r = __builtin_expect(!!(x), expect); \
ftrace_likely_update(&______f, ______r, \
expect, is_constant); \
______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) (__branch_check__(x, 1, __builtin_constant_p(x)))
# endif
# ifndef unlikely
# define unlikely(x) (__branch_check__(x, 0, __builtin_constant_p(x)))
# endif
从likely/unlikely宏的定义中可以看出除了第二个参数代表期望值不一样外,宏定义的其他参数都是一样的。最终宏在区块_ftrace_annotated_branch 中定义了一个ftrace_likely_data对象 描述了插入宏的代码信息,比如:
- 代码的位置信息
- 条件的信息
void ftrace_likely_update(struct ftrace_likely_data *f, int val,
int expect, int is_constant)
{
unsigned long flags = user_access_save();
/* A constant is always correct */
if (is_constant) {
f->constant++;
val = expect;
}
/*
* I would love to have a trace point here instead, but the
* trace point code is so inundated with unlikely and likely
* conditions that the recursive nightmare that exists is too
* much to try to get working. At least for now.
*/
trace_likely_condition(f, val, expect);
/* FIXME: Make this atomic! */
if (val == expect)
f->data.correct++;
else
f->data.incorrect++;
user_access_restore(flags);
}