2

我试图找出在评估条件语句(python 2.6.6)时否定布尔值是否有任何惩罚。我首先尝试了这个简单的测试(没有else分支)

>>> import timeit
>>> timeit.timeit("if not True: pass", number=100000)
0.011913061141967773
>>> timeit.timeit("if True: pass", number=100000)
0.018882036209106445

所以我认为结果是有偏差的,因为 pass 语句可能会被转换为 noop ,这至少是一些操作。

我做了第二次尝试,得到了这些结果:

>>> timeit.timeit("a=False\nif not a: pass\nelse: pass", number=100000)
0.02387714385986328
>>> timeit.timeit("a=False\nif a: pass\nelse: pass", number=100000)
0.015386819839477539
>>> timeit.timeit("a=True\nif a: pass\nelse: pass", number=100000)
0.02389812469482422
>>> timeit.timeit("a=True\nif not a: pass\nelse: pass", number=100000)
0.015424966812133789

我没想到会看到任何大的惩罚,但从这个结果来看,评估else分支似乎比隐式then分支便宜。而且差别很大!

所以第三次尝试返回这些结果:

>>> timeit.timeit("if True: a=1\nelse: a=1", number=100000)
0.022008895874023438
>>> timeit.timeit("if not True: a=1\nelse: a=1", number=100000)
0.022121906280517578

最后我得到了预期的结果。虽然出于好奇,我最后一次尝试:

>>> timeit.timeit("if False: a=1\nelse: a=1", number=100000)
0.02385997772216797
>>> timeit.timeit("if not False: a=1\nelse: a=1", number=100000)
0.02244400978088379

就是这样......我不知道为什么导致then分支的否定条件更快。

可能会发生什么?

所有这些结果都可以在我的计算机上重现,无论我运行它们多少次,我都会得到几乎相同的结果。

我认为第一个测试存在偏差,因为编译器可能已经else: pass完全删除了该部分。那可能吗?

可能所有这些结果都与 CPU 中的分支预测器有关吗?

还有其他可能的罪魁祸首吗?

4

1 回答 1

17

首先,“现实世界”新闻:如果您真的处于编写“if not”或“if ..pass else...”会影响应用程序性能的情况,我建议您进行一些认真的分析,并在本机代码中重写您的内部循环 - 使用 Cython 或 C(甚至其他选项,例如 Fortran - Python 擅长集成)。

否则,我会说这些是不相关的实现细节。用“dis”反汇编你的代码序列可以显示正在发生的事情——但是这个级别的优化在 Python 中没有任何实际价值——单个函数调用,即使是使用运算符隐式调用,也需要大约一个额外的数量级与执行if语句相比的时间。

这里:

>>> dis.dis(compile("if not True: pass", "None", "exec",))
  1           0 LOAD_NAME                0 (True)
              3 POP_JUMP_IF_TRUE         9
              6 JUMP_FORWARD             0 (to 9)
        >>    9 LOAD_CONST               0 (None)
             12 RETURN_VALUE        
>>> dis.dis(compile("if True: pass", "None", "exec",))
  1           0 LOAD_NAME                0 (True)
              3 POP_JUMP_IF_FALSE        9
              6 JUMP_FORWARD             0 (to 9)
        >>    9 LOAD_CONST               0 (None)
             12 RETURN_VALUE        
>>> 

As you can see, there is no difference in the bytecode generated for using "not" or not - but for the op of the jumping operator, which should take the same time in both cases.

于 2012-10-30T14:05:26.907 回答