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
为 null 的情况;即使后一种情况经常出现。)
最佳答案
不,builtin_expect 不会影响无竞争程序的语义。
特别是,编译器不得发出会执行 if
block 主体的代码,如果该代码具有无法撤消的副作用。除了性能之外,代码必须“好像”未使用 builtin_expect
。
对于您的具体示例:
if (__builtin_expect(p != 0, 1)) // line 1
p->access_object(); // line 2
p
如果为 null,则不能取消引用。那么在这种情况下 builtin_expect
的意义何在?它最多可以告诉编译器“p
可能不为空,因此可能会调用 access_object()
”。如果 access_object()
的定义是 inline
,编译器可能会尝试内联它,而如果你说“p
可能是 null, "编译器可能决定最好不要在此调用站点内联 access_object()
的代码,因为它不太可能被使用。
事实上,这会导致在实践中不直观地使用 builtin_expect
:您可以用它来表示“这段代码是慢速路径”,而不管它的“可能性”有多大。作为一个简单的例子,服务器程序可能会这样做:
if (__builtin_expect(is_allowed(user, request), 1))
process(request);
else
reject(request);
即使我们发现 50% 的请求是非法的并且将被拒绝,我们仍可能决定将“快乐路径”标记为可能已采用,因为我们不关心放慢拒绝速度。
关于c++ - 使用 `__builtin_expect` 会影响程序语义吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43184283/