Cygwin 64-bit环境下log2函数实现采用除法而非乘法的效率疑问
Cygwin 64-bit环境下log2函数实现采用除法而非乘法的效率疑问
这是个特别细致的观察,能注意到汇编层面的指令差异真的很厉害!我来针对你的问题逐一解答:
1. 为什么编译器会选择除以log2(e)而非乘以倒数?
这主要和你使用的GCC 3.6.4版本的优化策略有关:
- 从数学上看,
log2(x) = ln(x) / ln(2)和log2(x) = ln(x) * (1/ln(2))完全等价,两种计算的精度差异微乎其微(毕竟都是无理数的近似值)。 - 老版本GCC(比如3.x系列)的优化器对这类“常数除法转乘法”的场景覆盖还不够全面,尤其是针对标准库数学函数的转换逻辑,当时的实现更倾向于直接遵循数学公式的字面形式(用除法),而非优先考虑性能更优的乘法。
2. 浮点数乘法确实比除法快很多,那新版本会优化吗?
没错!现代CPU里,浮点数乘法只需要3-4个时钟周期,而除法要20个以上,性能差距非常明显。在GCC 8及以后的新版本中,优化器会自动把这种“除以已知常数”的操作替换成“乘以该常数的倒数”——你可以试试用新版本重新编译你的代码,会发现第一个循环里的divsd指令会直接变成mulsd,和你手动写的第二个循环逻辑完全一致。
3. 这是Cygwin 64-bit特有的问题吗?
绝对不是!这是老版本GCC的共性问题,和平台无关。在同一时期的Linux平台上用GCC 3.x编译同样的代码,也会生成类似的除法指令。而现在的GCC(不管是Cygwin、Linux还是其他x86-64平台)都会优先选择乘法实现,甚至会根据目标CPU的特性,直接调用硬件支持的log2指令(比如log2sd),进一步提升效率。
额外补充:为什么不直接调用硬件log2指令?
在GCC 3.x的时代,部分x86-64 CPU的硬件log2指令支持还不普及,加上当时的数学库实现更依赖成熟稳定的log函数转换逻辑,而非直接调用硬件指令。现在的编译器会自动适配目标CPU:如果CPU支持硬件log2指令就直接用,否则就用乘法转换的方式,兼顾兼容性和性能。
如果你的场景对性能敏感,强烈建议升级到较新的GCC版本,既能拿到更好的优化效果,也能用上现代CPU的新指令集~
我把你提供的测试代码和汇编重新格式化了下,方便参考:
测试代码
#include <stdio.h> #include <stdint.h> #include <math.h> int main() { uint32_t i; for (i = 1 ; i <= 10 ; i++) printf("log %u: %.20llf\n", i, log2((double)i)); for (i = 1 ; i <= 10 ; i++) printf("log %u: %.20llf\n", i, 1.442695040888963387 * log((double)i)); }
第一个循环(log2调用)的汇编
.L2: pxor %xmm0, %xmm0 cvtsi2sdl %ebx, %xmm0 call log movl %ebx, %edx movq %rsi, %rcx addl $1, %ebx divsd %xmm6, %xmm0 movq %xmm0, %r8 movapd %xmm0, %xmm2 call printf cmpl $11, %ebx jne .L2
第二个循环(手动转换)的汇编
.L3: pxor %xmm0, %xmm0 cvtsi2sdl %ebx, %xmm0 call log movl %ebx, %edx movq %rsi, %rcx addl $1, %ebx mulsd %xmm6, %xmm0 movq %xmm0, %r8 movapd %xmm0, %xmm2 call printf cmpl $11, %ebx jne .L3




