为什么 timeit() 函数 return 在处理函数和字符串表达式时会产生不同的结果?

Why does the timeit() function return different results when handed a function vs a string expression?

我正在 Python REPL 中试用 python timeit 函数。它可以通过两种方式为小块代码计时:作为可调用的,或作为引用的表达式。我想知道为什么以下代码会产生不同的计时结果。

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

我的直觉告诉我应该有更多 'overhead' 与引用的字符串变体相关联,因为它需要解释,但事实并非如此。但显然我的直觉是错误的..

这是另一个代码片段。调用可调用函数与计时引用的函数语句之间没有出现巨大的时间差异:

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

观察:

>>> 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 参数。 执行函数 costly 100 次需要 30 秒。 执行表达式 costly 1'000'000'000 (!) 次 花费了将近 30 秒。

为什么?因为第二个代码并没有执行函数costly!它唯一执行的是表达式 costly:注意缺少括号,这意味着它 不是 函数调用。表达式 costly 基本上是一个空操作(好吧,它只需要检查名称 "costly" 是否存在于当前范围内,仅此而已),这就是它如此之快的原因,如果 Python 足够聪明来优化它,表达式 costly (not costly()!) 的执行将是瞬间的!

在你的例子中,说 lambda *args: None 只是 定义 一个匿名函数,对吧?当您执行 这段代码 时,会创建一个新函数,但不会执行(为此,您应该 调用 它:(lambda *args: None)()).

因此,用 timeit.timeit("lambda *args: None")string "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

看 - 你甚至不能 运行!实际上,可能没有人可以,因为它有太多的递归!

为什么第一次调用成功了?因为它没有执行任何东西,它只是吐出很多新函数,不久之后就把它们全部去掉了。