我简直不敢相信我刚刚测量的:
python3 -m timeit -s "from math import sqrt" "sqrt(2)"
5000000 loops, best of 5: 42.8 nsec per loop
python3 -m timeit "2 ** 0.5"
50000000 loops, best of 5: 4.93 nsec per loop
这违背了任何直觉...事实恰恰相反!
macOS Catalina 上的 Python 3.8.3
Python 3 在编译时预先计算2 ** 0.5
的值,因为当时两个操作数都是已知的。然而,sqrt
的值在编译时是未知的,所以计算必然在运行时进行。
您没有计时计算2 ** 0.5
所需的时间,而只是计算加载常量所需的时间。
更公平的比较是
$ python3 -m timeit -s "from math import sqrt" "sqrt(2)"
5000000 loops, best of 5: 50.7 nsec per loop
$ python3 -m timeit -s "x = 2" "x**0.5"
5000000 loops, best of 5: 56.7 nsec per loop
我不确定是否有办法显示未优化的字节码。Python 首先将源代码解析为抽象语法树 (AST(:
>>> ast.dump(ast.parse("2**0.5"))
'Module(body=[Expr(value=BinOp(left=Num(n=2), op=Pow(), right=Num(n=0.5)))])'
更新:此特定优化现在直接应用于抽象语法树,因此字节码直接从类似
Module(body=Num(n= 1.4142135623730951))
ast
模块似乎未应用优化。
编译器采用 AST 并生成未优化的字节代码;在这种情况下,我相信它看起来(基于dis.dis("2**x")
和dis.dis("x**0.5")
的输出(像
LOAD_CONST 0 (2)
LOAD_CONST 1 (0.5)
BINARY_POWER
RETURN_VALUE
然后,窥视孔优化器对原始字节代码进行修改,该优化器可以将这 4 条指令减少到 2 条,如dis
模块所示。
然后,编译器从 AST 生成字节代码。
>>> dis.dis("2**0.5")
1 0 LOAD_CONST 0 (1.4142135623730951)
2 RETURN_VALUE
[虽然以下段落最初是出于优化字节码的想法编写的,但推理也适用于优化 AST。
由于运行时不会影响两个LOAD_CONST
和后续BINARY_POWER
指令的计算方式(例如,没有名称查找(,因此窥视孔优化器可以获取此字节码序列,执行2**0.5
本身的计算,并将前三条指令替换为立即加载结果的单个LOAD_CONST
指令。
为了增强切普纳的答案,这里有一个证明:
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import dis
>>> dis.dis('2 ** 0.5')
1 0 LOAD_CONST 2 (1.4142135623730951)
3 RETURN_VALUE
与。
>>> dis.dis('sqrt(2)')
1 0 LOAD_NAME 0 (sqrt)
3 LOAD_CONST 0 (2)
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 RETURN_VALUE
>>> dis.dis('44442.3123 ** 0.5')
0 LOAD_CONST 0 (210.81345379268373)
2 RETURN_VALUE
我不相信,44442.3123 ** 0.5
是在编译时预先计算的。我们应该更好地检查代码的 AST。
>>> import ast
>>> import math
>>> code = ast.parse("2**2")
>>> ast.dump(code)
'Module(body=[Expr(value=BinOp(left=Num(n=2), op=Pow(), right=Num(n=2)))])'
>>> code = ast.parse("math.sqrt(3)")
>>> ast.dump(code)
"Module(body=[Expr(value=Call(func=Attribute(value=Name(id='math', ctx=Load()), attr='sqrt', ctx=Load()), args=[Num(n=3)], keywords=[]))])"