问题:“ x
从此页面,我们知道:
链式比较比使用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
。为什么?
在搜索了该站点的一些帖子(如本篇文章)之后,我知道“仅评估一次”是的关键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®Xeon®CPU E5640 @ 2.67GHz上使用Python 2.7.6进行了测试。
回答 0
区别在于in 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
您定义的两个函数的最佳字节码将是
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等语言的实现未针对两种变体生成此字节码(或其等效的操作序列),则说明该字节码编译器的质量较差。从上面发布的字节码序列中获取是一个已解决的问题(我想在这种情况下,您需要做的就是不断折叠,消除无效代码以及对堆栈内容进行更好的建模;常见的子表达式消除也将是廉价且有价值的),而没有在现代语言实现中没有这样做的借口。
现在,碰巧该语言的所有当前实现都具有劣质的字节码编译器。但是您在编码时应该忽略这一点!假装字节码编译器很好,并编写最易读的代码。无论如何它可能足够快。如果不是这样,请首先寻找算法上的改进,然后再尝试Cython-与您可能应用的任何表达式级调整相比,在相同的工作量下将提供更多的改进。
回答 2
由于输出的差异似乎是由于缺乏优化所致,所以我认为在大多数情况下您应该忽略该差异-可能差异会消失。区别在于,y
只应评估一次,然后通过将其复制到堆栈上来解决该问题,这需要额外的费用POP_TOP
– LOAD_FAST
尽管有可能使用解决方案。
但是,重要的区别在于,如果对x<y and y<z
第二个y
进行评估,则如果应x<y
为true,则应评估两次,如果对的评估y
花费大量时间或具有副作用,则可能会产生影响。
在大多数情况下,x<y<z
尽管速度稍慢,但仍应使用。
回答 3
首先,您的比较几乎没有意义,因为没有引入两种不同的构造来提高性能,因此您不应基于此决定是否使用一个构造来代替另一个构造。
该x < y < z
结构:
- 其含义更清晰,更直接。
- 它的语义是您从比较的“数学意义”中所期望的:evalute
x
,y
并z
一次并检查是否整个条件成立。使用可以and
通过y
多次评估来更改语义,这可以更改结果。
因此,请根据您想要的语义以及是否相等来选择一个,以代替另一个。
这就是说:更多的反汇编代码确实 并不意味着慢的代码。但是,执行更多的字节码操作意味着每个操作都比较简单,但是需要主循环的迭代。这意味着,如果您正在执行的操作非常快(例如,您在那里执行的本地变量查找),那么执行更多字节码操作的开销可能会很重要。
但要注意,这个结果并没有在更一般的情况下举行,仅在“最坏情况”那你碰巧轮廓。正如其他人指出的那样,如果更改y
为花费更多时间的内容,您将看到结果更改,因为链接表示法仅对它进行一次评估。
总结:
- 性能之前要考虑语义。
- 考虑到可读性。
- 不要相信微型基准。始终使用不同种类的参数进行分析,以了解功能/表达式时序相对于所述参数的行为,并考虑您打算如何使用它。
从此页面,我们知道:
链式比较比使用
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
。为什么?
在搜索了该站点的一些帖子(如本篇文章)之后,我知道“仅评估一次”是的关键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®Xeon®CPU E5640 @ 2.67GHz上使用Python 2.7.6进行了测试。
区别在于in 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
您定义的两个函数的最佳字节码将是
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等语言的实现未针对两种变体生成此字节码(或其等效的操作序列),则说明该字节码编译器的质量较差。从上面发布的字节码序列中获取是一个已解决的问题(我想在这种情况下,您需要做的就是不断折叠,消除无效代码以及对堆栈内容进行更好的建模;常见的子表达式消除也将是廉价且有价值的),而没有在现代语言实现中没有这样做的借口。
现在,碰巧该语言的所有当前实现都具有劣质的字节码编译器。但是您在编码时应该忽略这一点!假装字节码编译器很好,并编写最易读的代码。无论如何它可能足够快。如果不是这样,请首先寻找算法上的改进,然后再尝试Cython-与您可能应用的任何表达式级调整相比,在相同的工作量下将提供更多的改进。
由于输出的差异似乎是由于缺乏优化所致,所以我认为在大多数情况下您应该忽略该差异-可能差异会消失。区别在于,y
只应评估一次,然后通过将其复制到堆栈上来解决该问题,这需要额外的费用POP_TOP
– LOAD_FAST
尽管有可能使用解决方案。
但是,重要的区别在于,如果对x<y and y<z
第二个y
进行评估,则如果应x<y
为true,则应评估两次,如果对的评估y
花费大量时间或具有副作用,则可能会产生影响。
在大多数情况下,x<y<z
尽管速度稍慢,但仍应使用。
首先,您的比较几乎没有意义,因为没有引入两种不同的构造来提高性能,因此您不应基于此决定是否使用一个构造来代替另一个构造。
该x < y < z
结构:
- 其含义更清晰,更直接。
- 它的语义是您从比较的“数学意义”中所期望的:evalute
x
,y
并z
一次并检查是否整个条件成立。使用可以and
通过y
多次评估来更改语义,这可以更改结果。
因此,请根据您想要的语义以及是否相等来选择一个,以代替另一个。
这就是说:更多的反汇编代码确实 并不意味着慢的代码。但是,执行更多的字节码操作意味着每个操作都比较简单,但是需要主循环的迭代。这意味着,如果您正在执行的操作非常快(例如,您在那里执行的本地变量查找),那么执行更多字节码操作的开销可能会很重要。
但要注意,这个结果并没有在更一般的情况下举行,仅在“最坏情况”那你碰巧轮廓。正如其他人指出的那样,如果更改y
为花费更多时间的内容,您将看到结果更改,因为链接表示法仅对它进行一次评估。
总结:
- 性能之前要考虑语义。
- 考虑到可读性。
- 不要相信微型基准。始终使用不同种类的参数进行分析,以了解功能/表达式时序相对于所述参数的行为,并考虑您打算如何使用它。