zan*_*ngw 128 python performance
从这个页面,我们知道:
链式比较比使用
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
Run Code Online (Sandbox Code Playgroud)
似乎x < y and y < z比它更快x < y < z.为什么?
在搜索了这个网站上的一些帖子之后(就像这个),我知道"仅评估一次"是关键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)
Run Code Online (Sandbox Code Playgroud)
输出是:
## 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)
Run Code Online (Sandbox Code Playgroud)
似乎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进行测试.
Rob*_*Rob 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
Run Code Online (Sandbox Code Playgroud)
zwo*_*wol 22
您定义的两个函数的最佳字节码
0 LOAD_CONST 0 (None)
3 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
因为没有使用比较的结果.让我们通过返回比较结果使情况更有趣.让我们在编译时也不知道结果.
def interesting_compare(y):
x = 1.1
z = 1.3
return x < y < z # or: x < y and y < z
Run Code Online (Sandbox Code Playgroud)
同样,两个版本的比较在语义上是相同的,因此两个构造的最佳字节码是相同的.最好我可以解决它,它看起来像这样.我用每个操作码之前和之后的堆栈内容注释了每一行,用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 --
Run Code Online (Sandbox Code Playgroud)
如果语言的实现,CPython,PyPy,无论如何,都不会为这两种变体生成这个字节码(或它自己的等效操作序列),这表明该字节码编译器的质量很差.从你发布到上面的字节码序列获取是一个解决的问题(我认为你需要的是这种情况下的常量折叠,死代码消除,以及更好的堆栈内容建模; 常见的子表达式消除也很便宜且有价值),并没有理由不在现代语言实现中这样做.
现在,恰好该语言的所有当前实现都具有质量差的字节码编译器.但你应该在编码时忽略它!假装字节码编译器是好的,并编写最可读的代码.无论如何,它可能足够快.如果不是,请首先寻找算法改进,然后再尝试Cython - 这将为您提供比您可能应用的任何表达式调整更多的改进.
由于输出的差异似乎是由于缺乏优化,我认为在大多数情况下你应该忽略这种差异 - 可能差异会消失.不同之处在于,y只应评估一次,并通过在堆栈上复制它来解决这个问题,这需要额外的POP_TOP- LOAD_FAST尽管可能使用解决方案.
然而,重要的区别在于,如果评估为真,x<y and y<z则第二个y应评估两次,如果x<y评估y需要相当长的时间或产生副作用,则会产生影响.
在大多数情况下你应该使用x<y<z尽管它有点慢.
首先,你的比较几乎毫无意义,因为没有引入两种不同的结构来提供性能改进,所以你不应该根据它来决定是否使用一种结构代替另一种.
该x < y < z结构:
x,y并且z 一次检查整个条件是否成立.通过多次and评估来改变语义y,这可以改变结果.因此,根据您想要的语义选择一个代替另一个,如果它们是等价的,那么一个是否比另一个更可读.
这表示:更多的反汇编代码也并不意味着慢的代码.但是,执行更多的字节码操作意味着每个操作都更简单,但它需要主循环的迭代.这意味着如果您正在执行的操作非常快(例如,当您在那里执行局部变量查找)时,执行更多字节码操作的开销可能很重要.
但是请注意,这个结果并没有在更一般的情况下举行,只到你碰巧配置文件中的"最坏情况".正如其他人所指出的那样,如果你改变y到需要花费更多时间的东西,你会发现结果会发生变化,因为链式符号只会评估一次.
总结: