Я использую следующий код, чтобы сделать тест, и он походит <медленнее это> =., кто-либо знает почему?
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
В 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, а не в <или> = проблема.
Я только что попробовал на 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
Итак, я предполагаю, что конфликты планирования с другими процессами являются здесь главной переменной.
Это был довольно интригующий вопрос. Я удалил 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, на которой я заметил предвзятость между операторами. Я, вероятно, получил большее смещение из-за масштабирования скорости процессора.
Ваш первый тест оценивается как истина, второй - как ложь. Возможно, в результате получается немного другая обработка.
Код операции 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.
Похоже, что в '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' всегда занимает больше времени. Но если вы попытаетесь изменить порядок вызовов или создания объектов, все ведет себя по-другому (и не по той схеме, которую я могу легко объяснить).
Это не ответ на ваш вопрос, просто замечание, что измерение таким способом может иметь присущие ему неточности.
Interesting! результат более заметен, если упростить выражение
используя IPython, я вижу, что x<=0
занимает 150ns, а x<0
занимает 320ns - в два раза дольше
другие сравнения x>0
и x>=0
, кажется, тоже занимают около 300ns
даже при 1000000 циклах результаты сильно колеблются