最近,当我在玩timeit和Python的指数时,我观察到了相当多的奇怪。
首先,我知道math.sin(1)=(e**1j).imag,我很好奇它们的相对速度。以下是我的发现:
>>> timeit('sin(1)', 'from math import sin')
0.12068345113220857
>>> timeit('(e**1j).imag', 'from math import e')
0.27201285586511403
>>> timeit('exp(1j).imag', 'from cmath import exp')
0.25259275173584683
>>> timeit('(2.718281828459045**1j).imag')
0.04272853350335026
这对我来说很奇怪。为什么使用数字本身和
**
比其他任何方法都快得多为什么比罪还快我知道这不是由于进口,我单独排除了。还应考虑:>>> (2.718281828459045**1j).imag
0.8414709848078965
>>> sin(1)
0.8414709848078965
所以,它给出了正确的答案。
我决定再深入一点,发现.imag是
(2.718281828459045**1j).imag
速度慢的真正罪魁祸首。事实上,>>> timeit('2.718281828459045**1j')
0.013987474140321865
它似乎不是1j特有的;我可以用2j或0.95j,得到同样的速度另外,它的速度甚至和复杂的正则乘法一样快!
>>> timeit('1*1j')
0.01617102287718808
>>> timeit('1*1')
0.016536898499907693
我完全糊涂了。当它至少做了同样多的工作(也计算cos)时,它怎么能比sin快得多呢?它怎么能像整数乘法那样快呢?我怀疑部分原因是时间开销的噪音(某处一定有一个循环),但即使这样也不能解释一切我希望你能帮助我理解。
最佳答案
您可以通过使用dis模块查看CPython生成的字节码来解释您的观察结果让我们看看。
********************************************************************************
from match import sin; sin(1)
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('sin',))
4 IMPORT_NAME 0 (match)
6 IMPORT_FROM 1 (sin)
8 STORE_NAME 1 (sin)
10 POP_TOP
12 LOAD_NAME 1 (sin)
14 LOAD_CONST 2 (1)
16 CALL_FUNCTION 1
18 POP_TOP
20 LOAD_CONST 3 (None)
22 RETURN_VALUE
********************************************************************************
from math import e; (e**1j).imag
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('e',))
4 IMPORT_NAME 0 (math)
6 IMPORT_FROM 1 (e)
8 STORE_NAME 1 (e)
10 POP_TOP
12 LOAD_NAME 1 (e)
14 LOAD_CONST 2 (1j)
16 BINARY_POWER
18 LOAD_ATTR 2 (imag)
20 POP_TOP
22 LOAD_CONST 3 (None)
24 RETURN_VALUE
********************************************************************************
from cmath import exp; exp(1j).imag
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('exp',))
4 IMPORT_NAME 0 (cmath)
6 IMPORT_FROM 1 (exp)
8 STORE_NAME 1 (exp)
10 POP_TOP
12 LOAD_NAME 1 (exp)
14 LOAD_CONST 2 (1j)
16 CALL_FUNCTION 1
18 LOAD_ATTR 2 (imag)
20 POP_TOP
22 LOAD_CONST 3 (None)
24 RETURN_VALUE
********************************************************************************
(2.718281828459045**1j).imag
1 0 LOAD_CONST 0 ((0.5403023058681398+0.8414709848078965j))
2 LOAD_ATTR 0 (imag)
4 RETURN_VALUE
正如您所看到的,上一个例子非常快,因为在创建字节码时,解释器正在将值转换为常量。实际上,除了调用
imag
之外,您上一次没有做任何工作。