为什么<慢于> =

时间:2010-07-30 07:04:50

标签: python performance optimization

我正在使用以下代码进行测试,它似乎<那个> =。慢,有人知道为什么吗?

import timeit
s = """
  x=5
  if x<0: pass
"""
  t = timeit.Timer(stmt=s)
  print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
#0.21 usec/pass
z = """
  x=5
  if x>=0: pass
"""
t2 = timeit.Timer(stmt=z)
print "%.2f usec/pass" % (1000000 * t2.timeit(number=100000)/100000)
#0.18 usec/pass

7 个答案:

答案 0 :(得分:32)

在Python 3.1.2中,有时&lt;比&gt; =更快。我尝试在反汇编程序中读取它,

import dis
def f1():
    x=5
    if x < 0: pass

def f2():
    x = 5
    if x >=0: pass

>>> dis.dis(f1)
  2           0 LOAD_CONST               1 (5) 
              3 STORE_FAST               0 (x) 

  3           6 LOAD_FAST                0 (x) 
              9 LOAD_CONST               2 (0) 
             12 COMPARE_OP               0 (<) 
             15 POP_JUMP_IF_FALSE       21 
             18 JUMP_FORWARD             0 (to 21) 
        >>   21 LOAD_CONST               0 (None) 
             24 RETURN_VALUE         
>>> dis.dis(f2)
  2           0 LOAD_CONST               1 (5) 
              3 STORE_FAST               0 (x) 

  3           6 LOAD_FAST                0 (x) 
              9 LOAD_CONST               2 (0) 
             12 COMPARE_OP               5 (>=) 
             15 POP_JUMP_IF_FALSE       21 
             18 JUMP_FORWARD             0 (to 21) 
        >>   21 LOAD_CONST               0 (None) 
             24 RETURN_VALUE         

代码几乎相同,但f1始终在第15行运行并跳转到21,f2始终运行15 - &gt; 18 - &gt; 21,因此在if语句中性能应该受到true / false的影响而不是&lt;或&gt; =问题。

答案 1 :(得分:5)

您的第一个测试评估为true,第二个测试评估为false。也许结果处理略有不同。

答案 2 :(得分:5)

COMPARE_OP操作码包含一个优化,其中两个操作数都是与C兼容的整数,在这种情况下,它只是与比较类型的switch语句进行内联比较:

if (PyInt_CheckExact(w) && PyInt_CheckExact(v)) {
        /* INLINE: cmp(int, int) */
        register long a, b;
        register int res;
        a = PyInt_AS_LONG(v);
        b = PyInt_AS_LONG(w);
        switch (oparg) {
        case PyCmp_LT: res = a <  b; break;
        case PyCmp_LE: res = a <= b; break;
        case PyCmp_EQ: res = a == b; break;
        case PyCmp_NE: res = a != b; break;
        case PyCmp_GT: res = a >  b; break;
        case PyCmp_GE: res = a >= b; break;
        case PyCmp_IS: res = v == w; break;
        case PyCmp_IS_NOT: res = v != w; break;
        default: goto slow_compare;
        }
        x = res ? Py_True : Py_False;
        Py_INCREF(x);
}

因此,比较中唯一的变化是通过switch语句的路由以及结果是True还是False。我的猜测是你只是看到由于CPU的执行路径(也许是分支预测)引起的变化,所以你看到的效果可能很容易消失,或者在其他版本的Python中反过来。

答案 3 :(得分:2)

我刚刚在Python 3.1.2中尝试过它 - 没有区别。

编辑:经过多次重复次数的尝试后,我发现这两个版本的值差异很大:

>>> import timeit
>>> s = """x=5
... if x<0: pass"""
>>> t = timeit.Timer(stmt=s)
>>> print ("%.2f usec/pass" % (1000000 * t.timeit(number=1000000)/100000))
1.48 usec/pass
>>>
>>> z = """x=5
... if x>=0: pass"""
>>> t2 = timeit.Timer(stmt=z)
>>> print ("%.2f usec/pass" % (1000000 * t.timeit(number=1000000)/100000))
0.59 usec/pass
>>>
>>> import timeit
>>> s = """x=5
... if x<0: pass"""
>>> t = timeit.Timer(stmt=s)
>>> print ("%.2f usec/pass" % (1000000 * t.timeit(number=1000000)/100000))
0.57 usec/pass
>>>
>>> z = """x=5
... if x>=0: pass"""
>>> t2 = timeit.Timer(stmt=z)
>>> print ("%.2f usec/pass" % (1000000 * t.timeit(number=1000000)/100000))
1.47 usec/pass

所以我猜这里与其他进程的调度冲突是这里的主要变量。

答案 4 :(得分:2)

对于某些激活它的“timeit”似乎有一些固有的开销(出乎意料的足够)。

尝试 -

import timeit

Times = 30000000

s = """
  x=5
  if x>=0: pass
"""

t1 = timeit.Timer( stmt=s )
t2 = timeit.Timer( stmt=s )
t3 = timeit.Timer( stmt=s )

print t1.timeit( number=Times )
print t2.timeit( number=Times )
print t3.timeit( number=Times )
print t1.timeit( number=Times )
print t2.timeit( number=Times )
print t3.timeit( number=Times )
print t1.timeit( number=Times )
print t2.timeit( number=Times )
print t3.timeit( number=Times )
print t1.timeit( number=Times )
print t2.timeit( number=Times )
print t3.timeit( number=Times )

在我的机器上输出是(一致的,无论我尝试多少循环 - 所以它可能不会恰好与机器上发生的其他事情重合) -

1.96510925271
1.84014169399
1.84004224001
1.97851123537
1.86845451028
1.83624929984
1.94599509155
1.85690220405
1.8338135154
1.98382475985
1.86861430713
1.86006657271

't1'总是需要更长时间。 但是如果你试图对调用或对象创建进行重新排序,那么事情就会有所不同(而不是我可以轻易解释的模式)。

这不是你问题的答案,只是观察到以这种方式测量可能会产生固有的不准确性。

答案 5 :(得分:1)

有趣!如果简化表达式,结果会更加强调

使用IPython我发现x<=0需要150ns而x<0需要320ns - 超过两倍

其他比较x>0x>=0似乎也需要300左右

即使超过1000000次循环,结果也会波动很多,但

答案 6 :(得分:1)

这是一个相当有趣的问题。我使用if cond: pass删除了v=cond,但并没有完全消除差异。我仍然不确定答案,但我找到了一个合理的理由:

switch (op) {
    case Py_LT: c = c <  0; break;
    case Py_LE: c = c <= 0; break;
    case Py_EQ: c = c == 0; break;
    case Py_NE: c = c != 0; break;
    case Py_GT: c = c >  0; break;
    case Py_GE: c = c >= 0; break;
}

这是来自Objects / object.c funcion convert_3way_to_object。请注意,&gt; =是最后一个分支;这意味着它一个人不需要退出跳跃。那个中断声明被消除了。它与shiki的反汇编中的0和5匹配。作为无条件中断,它可以通过分支预测来处理,但也可能导致更少的代码加载。

在这个级别,差异自然是高度机器特定的。我的测量结果不是很彻底,但这是C级的一点,我看到了操作员之间的偏见。我可能从CPU速度缩放中获得了更大的偏差。