为什么在Python 3中x**4.0比x**4快?

2024-04-19 17:07:40 发布

您现在位置:Python中文网/ 问答频道 /正文

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

$ 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,但如果我把x的力量提高到10.0作为一个浮点数,它只会在24.1~4左右移动。

我想这可能与浮点数转换和2的幂有关,但我不太清楚。

我注意到在这两种情况下,2的幂更快,我猜是因为这些计算对于解释器/计算机来说更为原生/简单。不过,有了漂浮物,它几乎不动了。2.0 => 24.1~4 & 128.0 => 24.1~4但是2 => 29 & 128 => 62


{a1}指出它不会发生在循环之外。我检查了一下,只有当基础升高时才会出现这种情况(从我所看到的情况来看)。你知道吗?

Tags: ofinloopfor情况rangecpython解释器
3条回答

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

>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625

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

Python 3int对象是一个成熟的对象,设计用于支持任意大小;因此,它们是handled as such on the C level(请参见如何在long_pow中将所有变量声明为PyLongObject *类型)。这也使得它们的指数化变得更加复杂,而且非常乏味,因为您需要处理它用来表示其值的ob_digit数组。(Source for the brave.——有关PyLongObjects的详细信息,请参见:Understanding memory allocation for large integers in Python。)

Pythonfloat对象,相反,可以转换为Cdouble类型(通过使用^{}),并且可以执行操作using those native types这很好,因为在检查了相关的边情况之后,它允许Python use the platforms' ^{}C's ^{}, that is)处理实际的指数运算:

/* 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作为Cdoubles

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之间的差异,所以,我想我也会处理这个注释,因为它很有趣。

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

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

你在这里看到的<type 'int'>做了同样的事情floats做了,它被安全地转换成一个C longwhen exponentiation is performed on it(如果编译器可以的话,int_pow还提示编译器将它们放入一个寄存器中,这样就可以产生不同的效果):

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

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

要查看与<type 'int'>s相比<type 'long'>s有多慢,如果在Python 2中将x名称包装在long调用中(本质上强制它使用long_pow,如Python 3中所示),速度增益将消失:

# <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的窥视孔优化器,为您折叠常量。无论哪种情况,都会得到相同的精确计时,因为没有实际的计算来找到指数运算的结果,只加载值:

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加载浮点256.0,而不是int256

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被转换成一个下线的浮点数。

>>> 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

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,太长,无法粘贴在此处,因此here's the link

它调用xnb_power槽,将y作为参数传递。

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

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);
    ...

相关问题 更多 >