最近,当我在玩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之外,您上一次没有做任何工作。

10-06 14:41