【发布时间】:2019-05-31 12:10:18
【问题描述】:
众所周知,乘法、整数除法和以 2 的幂为模可以更有效地重写为按位运算:
>>> x = randint(50000, 100000)
>>> x << 2 == x * 4
True
>>> x >> 2 == x // 4
True
>>> x & 3 == x % 4
True
在 C/C++ 和 Java 等编译语言中,测试表明按位运算通常比算术运算快。 (见here 和here)。但是,当我在 Python 中测试这些时,我得到了相反的结果:
In [1]: from random import randint
...: nums = [randint(0, 1000000) for _ in range(100000)]
In [2]: %timeit [i * 8 for i in nums]
7.73 ms ± 397 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [3]: %timeit [i << 3 for i in nums]
8.22 ms ± 368 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [4]: %timeit [i // 8 for i in nums]
7.05 ms ± 393 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [5]: %timeit [i >> 3 for i in nums]
7.55 ms ± 367 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [6]: %timeit [i % 8 for i in nums]
5.96 ms ± 503 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [7]: %timeit [i & 7 for i in nums]
8.29 ms ± 816 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
如您所见,按位运算比对应的算术运算要慢,尤其是模运算。我对另一组数字重复了这个测试,得到了相同的结果。是否有一个原因?如果重要的话,这些测试在 CPython 3.6.7 中。
【问题讨论】:
-
好吧,对于初学者来说,Python
int对象与 Cint完全不同,它们是对象。底层表示甚至不是 Cint,它是一个数字数组,如果你了解 C,请自己看看:github.com/python/cpython/blob/master/Include/longintrepr.h 这是因为 Pythonint对象是任意大小的 i>,不是固定大小。 -
除以 2 的幂并取模不是一个很好的测试;任何体面的 C 编译器都会将其编译为转变。 (或者对于已签名的
i,移位 + 东西以获得可能为负的i的舍入语义)。我不希望 CPython 会,但如果你想将它与 C 进行比较,请使用运行时变量除数。 (否则i / 12345仍然可以编译为快速乘法/移位。Why does GCC use multiplication by a strange number in implementing integer division?。现代 x86 CPU 具有非常高性能的乘法,大约与 3 次移位一样快。) -
@PeterCordes 我知道编译器会进行这样的优化,所以我使用
dis模块来检查i % <power of 2>没有进行位移,据我所知,它不是吨。但是,也许优化进一步下降,所以dis模块无法显示它......我不确定。 -
我对 Python 内部结构了解不多,但这种优化只有在可以对常数进行一次优化的情况下才可能/非常好,而不是每次都通过循环。检查
x & (x-1) == 0以检查 2 的幂,然后使用 POSIXffs()或 x86bsf/tzcnt之类的位扫描来获得移位计数可能不值得,除非实现期望 2 的幂很常见。 (然后仅用于除法,而不是乘法,除非输入具有多个肢体)。
标签: python optimization bitwise-operators micro-optimization