Я использую следующий код для выполнения теста, и кажется, что <медленнее, чем> =., Кто-нибудь знает почему?

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
19
Ilian Iliev 30 Июл 2010 в 11:04

7 ответов

Лучший ответ

В Python 3.1.2 иногда <быстрее, чем> =. Я пытаюсь прочитать это в дизассемблере,

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 -> 18 -> 21, так что на производительность должен влиять оператор true / false, а не <или> = проблема.

32
user326503user326503 30 Июл 2010 в 07:33

Ваш первый тест оценивается как true, второй - как false. Возможно, в результате будет несколько другая обработка.

5
djna 30 Июл 2010 в 07:11

Это был довольно интригующий вопрос. Я удалил 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. Обратите внимание, что> = является последней ветвью; это означает, что он сам по себе не нуждается в выходном прыжке. Это заявление о перерыве устранено. Это соответствует 0 и 5 в разборке Шики. Будучи безусловным разрывом, он может обрабатываться прогнозированием ветвлений, но это также может привести к уменьшению загрузки кода.

На этом уровне различие, естественно, будет зависеть от конкретной машины. Мои измерения не очень тщательные, но это была единственная точка на уровне C, в которой я увидел смещение между операторами. Я, вероятно, получил больший уклон от масштабирования скорости процессора.

1
Yann Vernier 30 Июл 2010 в 09:39

Интересный! результат будет более подчеркнут, если вы упростите выражение

Используя IPython, я вижу, что x<=0 занимает 150 нс, а x<0 - 320 нс - в два раза больше

Другие сравнения x>0 и x>=0, похоже, тоже требуют около 300 нс

Даже более 1000000 циклов результаты сильно колеблются, хотя

1
John La Rooy 30 Июл 2010 в 07:20

Код операции 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. Я предполагаю, что вы просто видите изменения из-за пути выполнения ЦП (и, возможно, предсказания ветвления), поэтому эффект, который вы видите, может так же легко исчезнуть или быть наоборот в других версиях Python.

5
Duncan 30 Июл 2010 в 07:57

Я только что попробовал это в Python 3.1.2 - никакой разницы нет.

РЕДАКТИРОВАТЬ: после многих попыток с большим количеством повторов, я вижу дико изменяющиеся значения в 3 раза для обеих версий:

>>> 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

Поэтому я предполагаю, что планирование конфликтов с другими процессами является основной переменной здесь.

2
Tim Pietzcker 30 Июл 2010 в 07:36

Кажется, что есть некоторые присущие «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» всегда занимает больше времени. Но если вы попытаетесь изменить порядок вызовов или создания объектов, все будет вести себя по-другому (и не так, как я мог бы легко объяснить).

Это не ответ на ваш вопрос, просто наблюдение, что измерения таким образом могут иметь присущие ему неточности.

2
Hexagon 30 Июл 2010 в 07:50