如果 Python3 否定与 XOR 相比,为什么它运行得更快?

nop*_*ole 6 python performance bit-manipulation python-3.x

我认为否定一个字或异或一个字1应该占用处理器的 1 个时钟周期,但如果使用 Python3,以下代码计算一个字的奇偶校验:

def parity(x: int) -> int:
    p = 0
    while x:
        p = ~p
        x = x & (x-1)
    return p & 1
Run Code Online (Sandbox Code Playgroud)

针对 10,000 个测试用例4?s运行将始终如一地报告平均运行时间,但如果是:

def parity(x: int) -> int:
    p = 0
    while x:
        p = p ^ 1
        x = x & (x-1)
    return p
Run Code Online (Sandbox Code Playgroud)

那么它是一贯的5?s。它实际上最后少了一个操作,不需要& 1

甚至

def parity(x: int) -> int:
    p = 0
    while x:
        p += 1
        x = x & (x-1)
    return p % 2
Run Code Online (Sandbox Code Playgroud)

一直在运行4?s。为了测试增量与加法,我将 更改p += 1p += 7并且它再次始终为4?s。使用 Python3 进行否定、递增或加法比 XOR 更快的原因是什么?(如果它有任何区别,它在Mac上)。

Hym*_*sco 5

事实证明,在 Python 中,并非所有运算符都是平等创建的。

您在问题中提到了“时钟周期”。诚然,典型的 CPU 可以在一个周期内完成许多此类任务,但是,Python 代码(在 CPython 中)的执行是由编译后的字节码决定的。这是一个操作(操作码)列表,它们映射到 CPython 运行时中的 C 代码。

C 代码这些部分的执行时间显然可能不同。事实上,您会发现它们都不与“1 个周期”操作相关联,而是与多个函数调用和分支语句相关联。

如果您反汇编您给出的两个函数,dis.dis(parity)您将看到它们的操作码在相关部分有何不同。

从第一个函数(求反)开始

10 LOAD_FAST                1 (p)
12 UNARY_INVERT
14 STORE_FAST               1 (p)
Run Code Online (Sandbox Code Playgroud)

从第二个函数(XOR):

10 LOAD_FAST                1 (p)
12 LOAD_CONST               2 (1)
14 BINARY_XOR
16 STORE_FAST               1 (p)
Run Code Online (Sandbox Code Playgroud)

最值得注意的是,UNARY_INVERT更改为BINARY_XOR. 检查Python/ceval.c中的master,switch (opcode) { ... }看看这些操作码和它们对应的代码有何不同。

这是一个小测试程序,用于比较 Python 中一些不同运算符的时间。

10 LOAD_FAST                1 (p)
12 UNARY_INVERT
14 STORE_FAST               1 (p)
Run Code Online (Sandbox Code Playgroud)

这是我在 CPython 3.7.2 64 位 (Windows) 中的结果

binary operators
+ 0.038782999999999956
- 0.03799190000000002
^ 0.05287609999999998
& 0.03716779999999997
| 0.0518267
unary operators
- 0.020763399999999987
~ 0.020213900000000007
not 0.01701140000000001
Run Code Online (Sandbox Code Playgroud)

从这些结果来看,它似乎^BINARY_XOR实际上是最慢的操作员(在测试的操作员中)