最近在使用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)时,怎么能比罪恶那么快呢?它怎么能像整数乘法一样快?我怀疑这部分是由于timeit开销产生的噪音(某处必须有一个循环),但是即使那样也不能解释所有问题。我将不胜感激。
答案 0 :(得分:3)
您可以通过使用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
以外,您实际上没有在上一次配置中做任何工作。