关于性能:为什么x ** 4.0比Python 3中的x ** 4快?

Why is x**4.0 faster than x**4 in Python 3?

为什么x**4.0x**4快?我正在使用CPython 3.5.2。

1
2
3
4
5
$ python -m timeit"for x in range(100):"" x**4.0"
  10000 loops, best of 3: 24.2 usec per loop

$ python -m timeit"for x in range(100):"" x**4"
  10000 loops, best of 3: 30.6 usec per loop

我试着改变我所提升的力量,看看它是如何运作的,例如,如果我把x提升到10或16的力量,它会从30跳到35,但是如果我把10.0作为一个浮动,它会在24.1~4之间移动。

我想这可能和浮点转换和2的幂有关,但我不知道。

我注意到,在这两种情况下,2的能力都更快,我想,因为这些计算对于口译员/计算机来说更为原始/简单。不过,有了漂浮物,它几乎不动了。2.0 => 24.1~4 & 128.0 => 24.1~42 => 29 & 128 => 62

Tigerhawkt3指出它不会发生在循环之外。我检查过了,情况只在基地被提升时发生(从我所见)。你知道吗?


Why is x**4.0 faster than x**4 in Python 3*?

python 3 int对象是一个完全成熟的对象,设计用于支持任意大小;因此,它们在C级别上是这样处理的(参见如何在long_pow中将所有变量声明为PyLongObject *类型)。这也使得它们的求幂更加复杂和乏味,因为您需要使用它用来表示其执行值的ob_digit数组。(勇敢者的来源。--有关PyLongObjects)的更多信息,请参见:了解python中大整数的内存分配。)

相反,python float对象可以转换为c double类型(使用PyFloat_AsDouble类型),并且可以使用这些本地类型执行操作。这很好,因为在检查了相关的边缘情况后,它允许python使用平台的pow(c的pow来处理实际的求幂:

1
2
3
4
5
6
7
/* Now iv and iw are finite, iw is nonzero, and iv is
 * positive and not equal to 1.0.  We finally allow
 * the platform pow to step in and do the rest.
 */
errno = 0;
PyFPE_START_PROTECT("pow", return NULL)
ix = pow(iv, iw);

其中,iviw是我们原来的PyFloatObjects,即c doubles。

For what it's worth: Python 2.7.13 for me is a factor 2~3 faster, and shows the inverse behaviour.

前面的事实也解释了python 2和3之间的差异,所以,我想我也会讨论这个注释,因为它很有趣。

在python 2中,您使用的是与python 3中的int对象不同的旧int对象(3.x中的所有int对象都是PyLongObject类型)。在python 2中,有一个区别取决于对象的值(或者,如果使用后缀L/l):

1
2
3
# Python 2
type(30)  # <type 'int'>
type(30L) # <type 'long'>

你在这里看到的float做的一样,当对它执行求幂时,它可以安全地转换为c long(int_pow还提示编译器如果可以的话,将它们放入寄存器中,这样做可能会有所不同):

1
2
3
4
5
static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
    register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */

这样可以获得良好的速度增益。

为了了解与s相比,s有多慢,如果在python 2中用long调用(本质上强制它使用long_pow与python 3中一样)包装x名称,则速度增益将消失:

1
2
3
4
5
6
# <type 'int'>
(python2) ? python -m timeit"for x in range(1000):"" x**2"      
10000 loops, best of 3: 116 usec per loop
# <type 'long'>
(python2) ? python -m timeit"for x in range(1000):"" long(x)**2"
100 loops, best of 3: 2.12 msec per loop

请注意,尽管一个片段将int转换为long,而另一个片段(如@pydsinger所指出的)则不是减速背后的促成力。long_pow的实施是。(仅与long(x)一起计时报表)。

[...] it doesn't happen outside of the loop. [...] Any idea about that?

这是CPython的窥视孔优化器,为您折叠常数。无论哪种情况,您都会得到相同的精确计时,因为没有实际计算来查找求幂结果,只加载值:

1
2
3
4
5
dis.dis(compile('4 ** 4', '', 'exec'))
  1           0 LOAD_CONST               2 (256)
              3 POP_TOP
              4 LOAD_CONST               1 (None)
              7 RETURN_VALUE

'4 ** 4.'生成相同的字节码,唯一的区别是LOAD_CONST加载的是float 256.0,而不是int 256

1
2
3
4
5
dis.dis(compile('4 ** 4.', '', 'exec'))
  1           0 LOAD_CONST               3 (256.0)
              2 POP_TOP
              4 LOAD_CONST               2 (None)
              6 RETURN_VALUE

所以时间是相同的。

*以上所有内容都只适用于Python的参考实现cpython。其他实现的执行方式可能不同。


如果我们看字节码,我们可以看到表达式是完全相同的。唯一的区别是常量的类型,它将是BINARY_POWER的参数。因此,最明显的原因是int被转换成了一个下线的浮点数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> def func(n):
...    return n**4
...
>>> def func1(n):
...    return n**4.0
...
>>> from dis import dis
>>> dis(func)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4)
              6 BINARY_POWER
              7 RETURN_VALUE
>>> dis(func1)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4.0)
              6 BINARY_POWER
              7 RETURN_VALUE

更新:让我们来看一下cpython源代码中的objects/abstract.c:

1
2
3
4
5
PyObject *
PyNumber_Power(PyObject *v, PyObject *w, PyObject *z)
{
    return ternary_op(v, w, z, NB_SLOT(nb_power),"** or pow()");
}

PyNumber_Power调用ternary_op,它太长了,无法粘贴到这里,所以这里是链接。

它称为xnb_power槽,把y作为论据。

最后,在objects/floatobject.c第686行的float_pow()中,我们看到参数在实际操作之前转换为c double

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static PyObject *
float_pow(PyObject *v, PyObject *w, PyObject *z)
{
    double iv, iw, ix;
    int negate_result = 0;

    if ((PyObject *)z != Py_None) {
        PyErr_SetString(PyExc_TypeError,"pow() 3rd argument not"
           "allowed unless all arguments are integers");
        return NULL;
    }

    CONVERT_TO_DOUBLE(v, iv);
    CONVERT_TO_DOUBLE(w, iw);
    ...


因为一个是正确的,另一个是近似值。

1
2
3
4
5
6
>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625