来自this page,我们知道:
链式比较比使用
and
运算符更快。 写x < y < z
而不是x < y and y < z
。
但是,我得到了不同的结果测试以下代码片段:
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y < z"
1000000 loops, best of 3: 0.322 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y and y < z"
1000000 loops, best of 3: 0.22 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y < z"
1000000 loops, best of 3: 0.279 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y and y < z"
1000000 loops, best of 3: 0.215 usec per loop
似乎x < y and y < z
比x < y < z
快。的为什么吗
在搜索此网站中的某些帖子后(例如this one),我知道“仅评估一次”是x < y < z
的关键,但我仍然感到困惑。为了进一步研究,我使用dis.dis
:
import dis
def chained_compare():
x = 1.2
y = 1.3
z = 1.1
x < y < z
def and_compare():
x = 1.2
y = 1.3
z = 1.1
x < y and y < z
dis.dis(chained_compare)
dis.dis(and_compare)
输出是:
## chained_compare ##
4 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
5 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
6 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
7 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 DUP_TOP
25 ROT_THREE
26 COMPARE_OP 0 (<)
29 JUMP_IF_FALSE_OR_POP 41
32 LOAD_FAST 2 (z)
35 COMPARE_OP 0 (<)
38 JUMP_FORWARD 2 (to 43)
>> 41 ROT_TWO
42 POP_TOP
>> 43 POP_TOP
44 LOAD_CONST 0 (None)
47 RETURN_VALUE
## and_compare ##
10 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
11 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
12 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
13 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 COMPARE_OP 0 (<)
27 JUMP_IF_FALSE_OR_POP 39
30 LOAD_FAST 1 (y)
33 LOAD_FAST 2 (z)
36 COMPARE_OP 0 (<)
>> 39 POP_TOP
40 LOAD_CONST 0 (None)
似乎x < y and y < z
的解释命令少于x < y < z
。我应该比x < y and y < z
更快地考虑x < y < z
吗?
在Intel(R)Xeon(R)CPU E5640 @ 2.67GHz上使用Python 2.7.6进行测试。
答案 0 :(得分:110)
不同之处在于x < y < z
y
仅评估一次。如果y是变量,这不会产生很大的差异,但是当它是函数调用时会发生这种情况,这需要一些时间来计算。
from time import sleep
def y():
sleep(.2)
return 1.3
%timeit 1.2 < y() < 1.8
10 loops, best of 3: 203 ms per loop
%timeit 1.2 < y() and y() < 1.8
1 loops, best of 3: 405 ms per loop
答案 1 :(得分:22)
您定义的两个函数的最佳字节码都是
0 LOAD_CONST 0 (None)
3 RETURN_VALUE
因为没有使用比较结果。让我们通过返回比较结果使情况更有趣。让我们在编译时也不知道结果。
def interesting_compare(y):
x = 1.1
z = 1.3
return x < y < z # or: x < y and y < z
同样,两个版本的比较在语义上是相同的,因此两个构造的最佳字节码是相同的。最好我可以解决它,它看起来像这样。我在每个操作码之前和之后用堆栈内容注释了每一行,用Forth表示法(右边的堆栈顶部,--
前后划分,尾随?
表示可能会或可能不会是那里)。请注意,RETURN_VALUE
会丢弃在返回值下面的堆栈上发生的所有内容。
0 LOAD_FAST 0 (y) ; -- y
3 DUP_TOP ; y -- y y
4 LOAD_CONST 0 (1.1) ; y y -- y y 1.1
7 COMPARE_OP 4 (>) ; y y 1.1 -- y pred
10 JUMP_IF_FALSE_OR_POP 19 ; y pred -- y
13 LOAD_CONST 1 (1.3) ; y -- y 1.3
16 COMPARE_OP 0 (<) ; y 1.3 -- pred
>> 19 RETURN_VALUE ; y? pred --
如果语言的实现,CPython,PyPy,等等,都不会为这两个变体生成这个字节码(或它自己的等效操作序列),表明该字节码编译器的质量很差 。从您发布到上面的字节码序列获取是一个已解决的问题(我认为这个案例所需要的只是constant folding,dead code elimination,并且可以更好地建模堆栈的内容; {{3}也是便宜而有价值的),并且没有理由不在现代语言实现中这样做。
现在,恰好该语言的所有当前实现都具有质量差的字节码编译器。但是在编码时你应该忽略!假装字节码编译器是好的,并编写最可读代码。无论如何,它可能足够快。如果不是,请先查找算法改进,然后再尝试common subexpression elimination - 这将为您提供比您可能应用的任何表达式调整更多的改进。
答案 2 :(得分:8)
Since the difference in the output seem to be due to lack of optimization I think you should ignore that difference for most cases - it could be that the difference will go away. The difference is because y
only should be evaluated once and that is solved by duplicating it on the stack which requires an extra POP_TOP
- the solution to use LOAD_FAST
might be possible though.
The important difference though is that in x<y and y<z
the second y
should be evaluated twice if x<y
evaluates to true, this has implications if the evaluation of y
takes considerable time or have side effects.
In most scenarios you should use x<y<z
despite the fact it's somewhat slower.
答案 3 :(得分:6)
首先,你的比较几乎没有意义,因为两个不同的结构不引入以提供性能改进,所以你不应该决定是否用一个来代替另一个在那。
x < y < z
构造:
x
,y
和z
一次并检查是否整个条件成立。使用and
通过多次评估y
来更改语义,这可以更改结果。因此,根据您想要的语义,选择一个代替另一个,如果它们是等效的,一个是否比另一个更可读。
这就是说:更多的反汇编代码不会意味着更慢的代码。 但是,执行更多的字节码操作意味着每个操作都更简单,但它需要主循环的迭代。 这意味着 if 您正在执行的操作非常快(例如,当您在那里执行局部变量查找)时,执行更多字节码操作的开销可能很重要。< / p>
但请注意,此结果确实不保留在更通用的情况下,仅适用于您碰巧分析的“最坏情况”。
正如其他人所指出的那样,如果你将y
改为需要花费更多时间的东西,你会发现结果会发生变化,因为链式符号只会对它进行一次评估。
汇总: