为何嵌套if比串行if执行速度更快?
if比串行if更快的性能差异分析 问题背景
我发现嵌套if比串行if执行速度更快,对此感到困惑。我有一段需要运行1亿次的代码:
if(b8 < 254) { if(b8 < 252) return data[b8]; return random_bit() ? data[b8] : -data[b8]; }
其中b8是uint8_t类型的随机数,random_bit()返回0-1的随机数。这段代码通过两次if判断返回值,我将其改写为:
if(b8 < 252) return data[b8]; if(b8 < 254) return random_bit() ? data[b8] : -data[b8];
改写后return data[b8]仅需一次if判断,但1亿次重复运行时程序慢了约0.01秒。我需要这段代码更快,哪怕只是0.01秒的提升,请问我对这段代码的理解存在误区吗?
另外,代码使用-Ofast编译,生成的汇编代码过于复杂难以理解:
嵌套版本汇编
.L2: ; nested cmpb $-3, %bl ja .L3 movzbl %bl, %r12d cmpb $-5, %bl ja .L4 movsbl sample_val.8(%r12), %ebx addq $8, %rsp movl %ebx, %eax popq %rbx popq %rbp popq %r12 popq %r13 ret
串行版本汇编
.L2: ; serialized cmpb $-5, %r14b jbe .L24 movl %r14d, %ebx ; inline random_bit() movl $8, %r12d movq cnt.7(%rip), %rdx movabsq $4611686018427387904, %r13 andl $1, %ebx cmpb $-3, %r14b ja .L19 testq %rdx, %rdx je .L6 shrq %rdx movq b64.6(%rip), %rax movq %rdx, cnt.7(%rip)
我认为两段代码逻辑等价,但GCC编译后生成了不同的汇编代码。
问题分析与解答
核心原因:串行版本存在无用的random_bit()执行
你的两段代码逻辑完全等价,但-Ofast优化下的GCC对串行版本做了不恰当的投机执行,导致性能损失:
嵌套版本的执行路径
- 仅当
b8 <254时,才会进入内部分支; - 仅当
b8 >=252且b8 <254时,才会调用random_bit(); b8 >=254的情况不会触碰到random_bit()的任何代码,完全跳过这部分逻辑。
- 仅当
串行版本的汇编暴露的问题
从串行版本的汇编代码可以看到:当第一个if(b8 <252)不成立(即b8 >=252)时,GCC提前执行了random_bit()的核心操作(比如访问全局变量cnt.7、b64.6的指令),之后才判断b8 <254。这意味着:- 当
b8 >=254时,这些random_bit()的操作完全是无用功,白白消耗CPU周期; - 1亿次循环中,
b8 >=254的情况占比约0.78%(2/256),累计下来就造成了0.01秒的性能差距。
- 当
为什么嵌套版本没有这个问题?
嵌套结构的分支逻辑层级更清晰,GCC可以明确判断:random_bit()的调用仅在b8 <254且b8 >=252的窄范围场景下才需要执行,因此不会触发投机执行,避免了无用操作。
优化串行版本的方法
如果你偏好串行写法,可以通过__builtin_expect给编译器提供分支概率提示,引导它生成更优的代码,避免不必要的投机执行:
if (__builtin_expect(b8 < 252, 1)) // 提示:此分支大概率命中 return data[b8]; if (__builtin_expect(b8 < 254, 0)) // 提示:此分支大概率不命中 return random_bit() ? data[b8] : -data[b8];
补充:分支预测的辅助影响
嵌套结构的分支预测连贯性更好:第一个分支b8 <254的命中率高达254/256,CPU分支预测器很容易准确预测,后续内部分支会在这个预测命中的上下文里执行,流水线停顿更少;而串行结构中,两次分支的独立性更强,可能增加分支预测的压力,但这不是本次性能差异的主要原因。
内容的提问来源于stack exchange,提问作者AngieJC

