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

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

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
18
задан Andrejs 20 August 2016 в 12:46
поделиться

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 в операторе if, а не в <или> = проблема.

32
ответ дан 30 November 2019 в 06:35
поделиться

Я только что попробовал на 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
ответ дан 30 November 2019 в 06:35
поделиться

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

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

1
ответ дан 30 November 2019 в 06:35
поделиться

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

5
ответ дан 30 November 2019 в 06:35
поделиться

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

5
ответ дан 30 November 2019 в 06:35
поделиться

Похоже, что в '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
ответ дан 30 November 2019 в 06:35
поделиться

Interesting! результат более заметен, если упростить выражение

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

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

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

1
ответ дан 30 November 2019 в 06:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: