Is “x < y < z” faster than “x < y and y < z”?
从这一页,我们知道:
Chained comparisons are faster than using the
and operator.
Writex < y < z instead ofx < y and y < z .
但是,我对以下代码段进行了不同的测试:
1 2 3 4 5 6 7 8 | $ 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 |
似乎
在搜索了这个站点的一些帖子(比如这个)之后,我知道"只评估一次"是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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) |
输出为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | ## 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) |
似乎
在Intel(R)Xeon(R)CPU [email protected]上使用python 2.7.6进行测试。
区别在于,在
1 2 3 4 5 6 7 8 | 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 2 | 0 LOAD_CONST 0 (None) 3 RETURN_VALUE |
因为没有使用比较结果。让我们返回比较结果,使情况更有趣。让我们也让结果在编译时无法知道。
1 2 3 4 | def interesting_compare(y): x = 1.1 z = 1.3 return x < y < z # or: x < y and y < z |
同样,比较的两个版本在语义上是相同的,因此对于这两个构造,最佳字节码是相同的。尽我所能,它看起来像这样。我在每一个操作码前后用第四个符号(右边的栈顶,
1 2 3 4 5 6 7 8 | 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(无论什么)没有为这两种变体生成这个字节码(或它自己的等价操作序列),这说明字节码编译器的质量很差。从你发布到上面的字节码序列中获取是一个解决了的问题(我认为这种情况下你所需要的就是不断的折叠、死代码消除和栈内容的更好的建模;常见的子表达式消除也将是廉价和有价值的),而且在现代语言impl中没有理由不这样做。喷发。
现在,所有当前的语言实现都有质量较差的字节码编译器。但是在编码时应该忽略这一点!假设字节码编译器是好的,并编写最可读的代码。无论如何,它可能足够快。如果不是的话,首先寻找算法上的改进,然后给cython第二次尝试——这将为相同的工作提供比你可能应用的任何表达式级别的调整更多的改进。
由于输出的差异似乎是由于缺乏优化造成的,我认为在大多数情况下,您应该忽略这一差异-这可能是差异将消失。区别在于,
不过,重要的区别在于,在
在大多数情况下,您应该使用
首先,您的比较几乎没有意义,因为没有引入两个不同的结构来提高性能,所以您不应该基于这一点来决定是否使用一个结构代替另一个结构。
因此,根据您想要的语义,选择一个代替另一个,如果它们是等效的,那么选择一个是否比另一个更可读。
这就是说:更多的反汇编代码并不意味着较慢的代码。然而,执行更多的字节码操作意味着每个操作都更简单,但它需要主循环的迭代。这意味着,如果您正在执行的操作非常快(例如,在本地执行变量查找),那么执行更多字节码操作的开销可能很重要。
但是请注意,这个结果不适用于更一般的情况,只适用于您碰巧分析的"最坏情况"。正如其他人所指出的,如果您将
总结:
- 在性能之前考虑语义。
- 考虑可读性。
- 不要相信微观基准。始终使用不同类型的参数进行分析,以查看函数/表达式计时相对于所述参数的行为,并考虑如何使用它。