“x< y< z”比“x< y&y< z”快?

时间:2015-12-01 07:31:35

标签: python performance

来自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 < zx < 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进行测试。

4 个答案:

答案 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 foldingdead 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构造:

  1. 其含义更清晰,更直接。
  2. 它的语义是您对比较的“数学意义”所期望的:evalute xyz 一次并检查是否整个条件成立。使用and通过多次评估y来更改语义,这可以更改结果
  3. 因此,根据您想要的语义,选择一个代替另一个,如果它们是等效的,一个是否比另一个更可读。

    这就是说:更多的反汇编代码不会意味着更慢的代码。 但是,执行更多的字节码操作意味着每个操作都更简单,但它需要主循环的迭代。 这意味着 if 您正在执行的操作非常快(例如,当您在那里执行局部变量查找)时,执行更多字节码操作的开销可能很重要。< / p>

    但请注意,此结果确实保留在更通用的情况下,仅适用于您碰巧分析的“最坏情况”。 正如其他人所指出的那样,如果你将y改为需要花费更多时间的东西,你会发现结果会发生变化,因为链式符号只会对它进行一次评估。

    汇总:

    • 在演出前考虑语义。
    • 考虑可读性。
    • 不要相信微观基准。始终使用不同类型的参数进行分析,以查看函数/表达式时序与所述参数的关系,并考虑您计划如何使用它。