GCC(和Clang也),为人工辅助分支预测提供此__builtin_expect
,如here所述。非正式地,人们通过以下方式解释其语义:"编译器只是无条件地处理指示的分支,如果条件应该与指示的不同,则会发生昂贵的回滚"。
但如果我有一段代码如下:
if (__builtin_expect(p != 0, 1)) // line 1
p->access_object(); // line 2
如果我按字面意思处理上面的非正式解释,编译器只需执行第2行而不等待第1行中的条件计算,因此如果指针恰好为null,则会导致未定义的行为(空指针取消引用)
我的问题是,如果我使用__builtin_expect
,我还能得到保证吗,我的防御性检查有效吗?如果是这样的话,如果我在防御性检查中使用__builtin_expect
,我是否可以获得任何运行时间的好处?
(注意:我这样使用__builtin_expect
的目标是在p
非空的情况下获得最大性能,但代价是放慢速度(即使是数量级) p
为空的情况;即使后一种情况经常出现。)
答案 0 :(得分:4)
不,builtin_expect不会影响无竞赛计划的语义。
特别是,如果代码具有无法撤消的副作用,编译器不得发出执行if
块主体的代码。除了性能之外,代码必须“好像”builtin_expect
未被使用。
对于您的具体示例:
if (__builtin_expect(p != 0, 1)) // line 1
p->access_object(); // line 2
如果 p
为空,则无法取消引用。那么builtin_expect
在这种情况下有什么意义呢?它能做的最多就是告诉编译器“p
可能不是null,因此可能会调用access_object()
。”如果access_object()
的定义是inline
,编译器可能会尝试内联它,而如果你说“p
可能是空的”,编译器可能会认为最好不要内联这个呼叫站点的access_object()
代码,因为它不太可能被使用。
事实上,这导致在实践中非直观地使用builtin_expect
:你可以用来表示“这段代码是慢路径”,而不管它是多么“可能”。作为一个简单的例子,服务器程序可能会这样做:
if (__builtin_expect(is_allowed(user, request), 1))
process(request);
else
reject(request);
即使我们发现50%的请求是非法的并且会被拒绝,我们仍然可能会决定标记“快乐路径”,因为我们不关心减慢拒绝。