Подтвердить что ты не робот

Почему <медленнее, чем> =

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

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

Ответ 1

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

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, поэтому на производительность должно влиять истинное/ложное выражение if, а не < или >= проблема.

Ответ 2

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

Ответ 3

Код операции COMPARE_OP содержит оптимизацию для случая, когда оба операнда являются целыми числами, совместимыми с C, и в этом случае он просто выполняет сравнение inline с оператором 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.

Ответ 4

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

EDIT: после многих попыток с большим количеством повторений я вижу дико меняющиеся значения в 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

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

Ответ 5

Похоже, что некоторые временные ограничения для некоторых активизаций (как это было достаточно неожиданно) имеют определенные накладные расходы.

Попробуйте -

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

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

Ответ 6

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

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

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

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

Ответ 7

Это был довольно интригующий вопрос. Я удалил 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;
}

Это из объектов /object.c funcion convert_3way_to_object. Заметим, что >= последняя ветвь; это означает, что он, один, не нуждается в выходе. Это утверждение break устранено. Он совпадает с 0 и 5 в демонстрации shiki. Будучи безусловным перерывом, он может обрабатываться предсказанием ветвления, но это также может привести к уменьшению загрузки кода.

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