为何编译器不将浮点数除法n/2.0优化为乘法n*0.5以提升性能?
问题:为何编译器不将
num / 2.0f优化为等价的num * 0.5f? 我一直认为num * 0.5f与num / 2.0f是等价的,因为我觉得编译器足够智能,可以将除法优化掉。于是我今天验证了这个想法,结果却让我困惑。
给定以下示例代码:
float mul(float num) { return num * 0.5f; } float div(float num) { return num / 2.0f; }
x86-64架构下的clang和gcc编译器生成的汇编代码如下(默认无优化编译):
mul(float): push rbp mov rbp, rsp movss DWORD PTR [rbp-4], xmm0 movss xmm1, DWORD PTR [rbp-4] movss xmm0, DWORD PTR .LC0[rip] mulss xmm0, xmm1 pop rbp ret div(float): push rbp mov rbp, rsp movss DWORD PTR [rbp-4], xmm0 movss xmm0, DWORD PTR [rbp-4] movss xmm1, DWORD PTR .LC1[rip] divss xmm0, xmm1 pop rbp ret
将这些代码输入到代码分析工具后,得到Skylake架构下的预测吞吐量分别为9.0和16.0个CPU周期。
我的问题是:为何编译器不将除法函数转换为等价的乘法函数?既然右侧是常量值,这应该是可以实现的,不是吗?
附:我在Rust中也做了等价测试,结果分别为4.0和11.0个CPU周期。
解答
核心原因是你使用了**默认无优化(-O0)**的编译模式:
无优化模式的目标:-O0是编译器的默认编译级别,它的核心目的是保留源代码和汇编指令的一一对应关系,方便调试时逐行跟踪代码执行。此时编译器不会进行任何会改变代码结构的优化,包括将常量除法替换为乘法这类优化,哪怕数学上完全等价。
优化模式下会自动转换:当你开启-O2、-O3等优化级别后,编译器会识别到
/2.0f等价于*0.5f——因为2.0和0.5都是二进制浮点数可以精确表示的数值(属于2的整数次幂相关值),转换不会带来任何精度损失,所以会自动将除法指令替换为乘法指令,此时两个函数生成的汇编完全一致,吞吐量也会相同。
比如用gcc -O2编译上述代码,生成的汇编会是:
mul(float): mulss xmm0, DWORD PTR .LC0[rip] ret div(float): mulss xmm0, DWORD PTR .LC0[rip] ret
两个函数都使用了mulss指令,执行效率完全一致。
- 关于浮点数精度的补充:如果除数不是2的整数次幂,编译器可能不会直接替换为乘法,因为此时倒数无法精确表示为浮点数,替换会带来精度偏差。但对于2的幂次除数,不存在这个问题,优化是安全的。
内容的提问来源于stack exchange,提问作者MenoraMenora
相关产品推荐
相关产品推荐

