2024-05-14 19:47:15 发布
网友
我简直不敢相信我刚才测量的结果:
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
>>> dis.dis('44442.3123 ** 0.5') 0 LOAD_CONST 0 (210.81345379268373) 2 RETURN_VALUE
我不相信44442.3123 ** 0.5是在编译时预计算的。我们最好检查代码的AST
44442.3123 ** 0.5
>>> 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=[]))])"
Python3在编译时预计算2 ** 0.5的值,因为两个操作数在当时都是已知的。但是sqrt的值在编译时是未知的,因此计算必须在运行时进行
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)))])'
更新:这个特定的优化现在被应用directly to the abstract syntax tree,因此字节码是直接从以下内容生成的
Module(body=Num(n= 1.4142135623730951))
ast模块似乎没有应用优化
ast
编译器接受AST并生成未优化的字节码;在这种情况下,我相信它看起来(基于dis.dis("2**x")和dis.dis("x**0.5")的输出)像
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模块所示
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替换前三条指令立即加载结果的指令
LOAD_CONST
BINARY_POWER
2**0.5
为了增强chepner's answer,这里有一个证明:
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
vs
>>> 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
我不相信
44442.3123 ** 0.5
是在编译时预计算的。我们最好检查代码的ASTPython3在编译时预计算
2 ** 0.5
的值,因为两个操作数在当时都是已知的。但是sqrt
的值在编译时是未知的,因此计算必须在运行时进行计算
2 ** 0.5
所需的时间不是计时,而是加载常数所需的时间更公平的比较是
我不确定是否有办法显示未优化的字节码。Python首先将源代码解析为抽象语法树(AST):
更新:这个特定的优化现在被应用directly to the abstract syntax tree,因此字节码是直接从以下内容生成的
ast
模块似乎没有应用优化编译器接受AST并生成未优化的字节码;在这种情况下,我相信它看起来(基于dis.dis("2**x")
和dis.dis("x**0.5")
的输出)像原始字节码然后由窥视孔优化程序修改,它可以将这4条指令减少为2条,如
dis
模块所示然后,编译器从AST生成字节码
[虽然以下段落最初是为了优化字节码而编写的,但推理也适用于优化AST。]
由于运行时没有任何东西会影响两条
LOAD_CONST
和后面的BINARY_POWER
指令的计算方式(例如,没有名称查找),因此窥视孔优化器可以获取这一字节码序列,执行2**0.5
本身的计算,并用一条LOAD_CONST
替换前三条指令立即加载结果的指令为了增强chepner's answer,这里有一个证明:
vs
相关问题 更多 >
编程相关推荐