为何小数值场景下乘法运算比位运算移位更快?
位运算移位为何在小数值下比乘法慢?
现象观察
先看两组性能测试的结果:
第一组测试取多次运行的最小值:
from timeit import repeat for e in ['x*2 ', 'x<<1'] * 3: print(e, min(repeat(e, 'x=5')))
输出:
x*2 0.015567475988063961 x<<1 0.024531989998649806 x*2 0.01551242297864519 x<<1 0.024578287004260346 x*2 0.015560572996037081 x<<1 0.02448918900336139
可以看到小数值x=5时,x*2的耗时明显比x<<1低,且这个差异在x到10亿时都存在,仅随倍数增大而缩小。
第二组测试取多次运行的最大值:
for e in ['x*2 ', 'x<<1'] * 3: print(e, max(repeat(e, 'x=5')))
输出:
x*2 0.054458492988487706 x<<1 0.02453691599657759 x*2 0.015550968993920833 x<<1 0.0246038619952742 x*2 0.015542584005743265 x<<1 0.024583352991612628
这里x*2首次执行耗时远高于移位,后续则和移位耗时相近。
原因解析
1. 小整数场景下的解释器优化差异
Python对**-5到256之间的小整数**有全局缓存池,避免重复创建对象。而乘法和移位的字节码实现逻辑存在差异:
BINARY_MULTIPLY(乘法操作码)针对小整数有更直接的快速路径,计算结果直接命中缓存,几乎不需要额外操作;BINARY_LSHIFT(移位操作码)虽然结果相同,但执行时会额外做一些检查(比如移位位数的合法性、整数内部存储格式的适配),这些额外步骤在小数值场景下的开销占比更高,导致整体耗时增加。
另外,现代CPU的乘法指令本身已经非常高效,对于2的幂次乘法,CPU会自动优化为移位操作,但Python解释器的层面开销(操作码处理、对象检查等)掩盖了硬件层面的优势,小数值下解释器的逻辑差异成为性能主导因素。
当x增大到10亿这类超出小整数缓存池的数值时,乘法和移位都需要创建新的大整数对象,此时对象创建的开销占比更大,两者的操作逻辑差异被稀释,耗时差也就缩小了。
2. 首次执行的字节码编译开销
timeit.repeat执行表达式时,首次需要将字符串形式的代码编译为字节码(生成PyCodeObject),这个编译过程存在固定开销:
x*2的表达式编译逻辑相对复杂,首次编译耗时更高;x*2后续执行时复用了已经编译好的字节码,耗时直接降到和移位相近的水平;- 移位表达式的编译开销本身较小,所以首次和后续执行的耗时差异不明显。
验证:查看字节码
可以用dis模块对比两个表达式的字节码:
import dis dis.dis("x*2") dis.dis("x<<1")
输出:
# x*2的字节码 1 0 LOAD_NAME 0 (x) 2 LOAD_CONST 0 (2) 4 BINARY_MULTIPLY 6 RETURN_VALUE # x<<1的字节码 1 0 LOAD_NAME 0 (x) 2 LOAD_CONST 0 (1) 4 BINARY_LSHIFT 6 RETURN_VALUE
字节码结构几乎一致,但底层C实现的逻辑差异才是性能差的核心。
内容的提问来源于stack exchange,提问作者Woody1193
相关产品推荐
相关产品推荐

