Почему функция timeit () возвращает разные результаты при передаче функции против строкового выражения? - PullRequest
2 голосов
/ 15 марта 2019

Я опробую функцию python timeit в моем Python REPL. Он может рассчитывать небольшие фрагменты кода двумя способами: либо как вызываемое, либо как выражение в кавычках. Я хотел бы знать, почему следующий код дает разные результаты синхронизации.

>>> import timeit
>>> timeit.timeit("lambda *args: None")
0.058281898498535156
>>> timeit.timeit(lambda *args: None)
0.0947730541229248
>>>

Моя интуиция говорит мне, что должно быть больше «накладных расходов», связанных с вариантом строки в кавычках, потому что это требует интерпретации, но это не так. Но, видимо, моя интуиция ошибочна ..

Вот еще один фрагмент кода. Не возникает огромной разницы во времени между вызовом вызываемой функции и синхронизацией оператора функции в кавычках:

>>> def costly_func():
...     return list(map(lambda x: x^2, range(10)))
... 
>>> import timeit
>>> timeit.timeit(costly_func)
2.421797037124634
>>> timeit.timeit("list(map(lambda x: x^2, range(10)))")
2.3588619232177734

1 Ответ

1 голос
/ 15 марта 2019

Наблюдение:

>>> def costly():
...  return list(map(str, list(range(1_000_000))))
...
>>> timeit.timeit(costly, number=100)
30.65105245400082
>>> timeit.timeit('costly', number=1_000_000_000, globals=globals())
27.45540758000061

Посмотрите на аргумент number. потребовалось 30 секунд для выполнения функции costly 100 раз. потребовалось почти 30 секунд, чтобы выполнить выражение costly 1'000'000'000 (!) Раз.

Почему?Поскольку второй код не не выполняет функцию costly!Единственное, что он выполняет, это выражение costly: обратите внимание на отсутствие скобок, что означает, что это , а не вызов функции.Выражение costly в основном не работает (ну, это просто требует проверки, существует ли имя «дорогой» в текущей области, вот и все), поэтому это так быстро, и если Python был достаточно умен, чтобы оптимизировать еговыполнение выражения costly ( не costly()!) было бы мгновенным!

В вашем случае выражение lambda *args: None просто определяет anанонимная функция, верно?Когда вы выполняете этот точный код , новая функция создается, но не выполняется (для этого вам нужно вызвать it: (lambda *args: None)()).

Таким образом, синхронизация строки "lambda *args: None" с timeit.timeit("lambda *args: None") в основном проверяет, насколько быстро Python может выплевывать новые анонимные функции.

Время самой функции с timeit.timeit(lambda *args: None) проверяет, насколько быстро Python может выполнить * существующую функцию.

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

Возьмите этот код, например:

def Ackermann(m, n):
    if m == 0:
        return n + 1
    if m > 0:
        if n == 0:
            return Ackermann(m - 1, 1)
        elif n > 0:
            return Ackermann(m - 1, Ackermann(m, n - 1))

Если вы поместите этот точный код в строку и timeit его, вы получите что-то вроде этого:

>>> code = """def Ackermann(m, n):
...     if m == 0:
...         return 0
...     if m > 0:
...         if n == 0:
...             return Ackermann(m - 1, 1)
...         elif n > 0:
...             return Ackermann(m - 1, Ackermann(m, n - 1))"""
>>> timeit.timeit(code, number=1_000_000)
0.10481472999890684

Теперь попробуйте timeit саму функцию:

>>> timeit.timeit(lambda : Ackermann(6, 4), number=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/timeit.py", line 232, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/timeit.py", line 176, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
  File "<stdin>", line 1, in <lambda>
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 8, in Ackermann
  [Previous line repeated 1 more time]
  File "<stdin>", line 6, in Ackermann
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 6, in Ackermann
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 8, in Ackermann
  [Previous line repeated 983 more times]
  File "<stdin>", line 6, in Ackermann
  File "<stdin>", line 2, in Ackermann
RecursionError: maximum recursion depth exceeded in comparison

Смотрите - вы даже не можете запустить это!На самом деле, вероятно, никто не может, так как это так много рекурсии!

Почему первый вызов прошел успешно?Поскольку он ничего не выполнял, он просто выплевывал множество новых функций и вскоре избавлялся от всех них.

...