为什么在Python 3中复指数运算如此之快?

时间:2019-03-11 05:58:42

标签: python python-3.x performance

最近在使用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开销产生的噪音(某处必须有一个循环),但是即使那样也不能解释所有问题。我将不胜感激。

1 个答案:

答案 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以外,您实际上没有在上一次配置中做任何工作。