Почему удаление else замедляет мой код?

Рассмотрим следующие функции:

def fact1(n):
    if n < 2:
        return 1
    else:
        return n * fact1(n-1)

def fact2(n):
    if n < 2:
        return 1
    return n * fact2(n-1)

Они должны быть эквивалентны. Но есть разница в производительности:

>>> T(lambda : fact1(1)).repeat(number=10000000)
[2.5754408836364746, 2.5710129737854004, 2.5678811073303223]
>>> T(lambda : fact2(1)).repeat(number=10000000)
[2.8432059288024902, 2.834425926208496, 2.8364310264587402]

Версия без else на 10% медленнее. Это довольно существенно. Зачем?

3 ответа

Решение

Для меня они практически одинаковой скорости: (Python 2.6.6 на Debian)

In [4]: %timeit fact1(1)
10000000 loops, best of 3: 151 ns per loop

In [5]: %timeit fact2(1)
10000000 loops, best of 3: 154 ns per loop

Байт-код также очень похож:

In [6]: dis.dis(fact1)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (2)
              6 COMPARE_OP               0 (<)
              9 JUMP_IF_FALSE            5 (to 17)
             12 POP_TOP             

  3          13 LOAD_CONST               2 (1)
             16 RETURN_VALUE        
        >>   17 POP_TOP             

  5          18 LOAD_FAST                0 (n)
             21 LOAD_GLOBAL              0 (fact)
             24 LOAD_FAST                0 (n)
             27 LOAD_CONST               2 (1)
             30 BINARY_SUBTRACT     
             31 CALL_FUNCTION            1
             34 BINARY_MULTIPLY     
             35 RETURN_VALUE        
             36 LOAD_CONST               0 (None)
             39 RETURN_VALUE        

In [7]: dis.dis(fact2)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (2)
              6 COMPARE_OP               0 (<)
              9 JUMP_IF_FALSE            5 (to 17)
             12 POP_TOP             

  3          13 LOAD_CONST               2 (1)
             16 RETURN_VALUE        
        >>   17 POP_TOP             

  4          18 LOAD_FAST                0 (n)
             21 LOAD_GLOBAL              0 (fact)
             24 LOAD_FAST                0 (n)
             27 LOAD_CONST               2 (1)
             30 BINARY_SUBTRACT     
             31 CALL_FUNCTION            1
             34 BINARY_MULTIPLY     
             35 RETURN_VALUE        

Разница лишь в том, что версия с else включает код для возврата None в случае, если управление достигает конца тела функции.

Что здесь происходит, так это fact2 имеет хэш-конфликт с __name__ в вашем модуле глобалы. Это делает поиск глобального fact2 немного медленнее.

>>> [(k, hash(k) % 32) for k in globals().keys() ]
[('__builtins__', 8), ('__package__', 15), ('fact2', 25), ('__name__', 25), ('fact1', 26), ('__doc__', 29)]

то есть тот же ответ, что и для " Почему раннее возвращение медленнее, чем раньше? кроме того, что там хэш-конфликт был с __builtins__

Я подвергаю сомнению сроки. Эти две функции не повторяются сами по себе. fact1 и fact2 оба называют факт, который не показан.

Как только это исправлено, дизассемблирование (как в Py2.6, так и в Py2.7) показывает, что оба работают с одними и теми же кодами операций, за исключением имени вернувшейся в функцию. Выбор имени вызывает небольшую разницу во времени, потому что fact1 может вставляться в словарь модуля без коллизий имен, в то время как *fact2) может иметь значение хеша, которое вступает в противоречие с другим именем в модуле.

Другими словами, любые различия, которые вы видите во времени, не связаны с выбором того, присутствует ли условие else:-)

Другие вопросы по тегам