如果您想了解编译器在做什么,您只需要拉出一些程序集.我推荐这个网站(我已经输入了问题中的代码):https://godbolt.org/g/FwZZOb.
第一个示例更有趣.
int div(unsigned int num, unsigned int num2) {
if( num >= num2 ) return num % num2;
return num;
}
int div2(unsigned int num, unsigned int num2) {
return num % num2;
}
生成:
div(unsigned int, unsigned int): # @div(unsigned int, unsigned int)
mov eax, edi
cmp eax, esi
jb .LBB0_2
xor edx, edx
div esi
mov eax, edx
.LBB0_2:
ret
div2(unsigned int, unsigned int): # @div2(unsigned int, unsigned int)
xor edx, edx
mov eax, edi
div esi
mov eax, edx
ret
基本上,出于非常具体和合乎逻辑的原因,编译器将not优化掉分支.如果整数除法与比较的成本大致相同,那么分支将非常没有意义.但是整数除法(通常和整数除法一起进行)实际上非常昂贵:http://www.agner.org/optimize/instruction_tables.pdf.这些数字因体系 struct 和整数大小的不同而有很大差异,但通常的延迟可能在15到接近http://www.agner.org/optimize/instruction_tables.pdf个周期之间.
通过在执行模数之前获取一个分支,实际上可以节省大量工作.不过请注意:编译器也不会将没有分支的代码转换为程序集级别的分支.这是因为分支也有一个缺点:如果模数最终仍然是必需的,那么你只是浪费了一点时间.
在不知道idx < idx_max
为真的相对频率的情况下,无法对正确的优化做出合理的确定.因此,编译器(GCC和Clang做同样的事情) Select 以相对透明的方式映射代码,将此 Select 权留给开发人员.
因此,分支机构可能是一个非常合理的 Select .
第二个分支应该是完全没有意义的,因为比较和赋值are的可比成本.也就是说,您可以在链接中看到,如果编译器引用了该变量,则它们仍然不会执行此优化.如果值是局部变量(如您演示的代码中所示),则编译器将优化分支.
总之,第一段代码可能是一个合理的优化,第二段代码可能只是一个疲惫的程序员.