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

ari*_*nai 163 python performance python-3.x python-internals python-3.5

为什么x**4.0比快x**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
Run Code Online (Sandbox Code Playgroud)

我尝试改变我所提出的力量,看看它是如何动作的,例如,如果我将x提升到10或16的力量,它会从30跳到35,但如果我将10.0提升为浮动,它只是移动大约24.1~4.

我想它可能与浮点转换和2的幂有关,但我真的不知道.

我注意到在两种情况下,2的幂都更快,我想因为这些计算对于解释器/计算机而言更加原生/容易.但是,对于浮子,它几乎没有移动.2.0 => 24.1~4 & 128.0 => 24.1~4 2 => 29 & 128 => 62


TigerhawkT3指出它不会发生在循环之外.我检查过,当基地升起时,情况才会发生(从我所看到的情况).有什么想法吗?

Jim*_*ard 160

为什么比Python 3 *x**4.0 更快x**4

Python 3 int对象是一个完全成熟的对象,旨在支持任意大小; 由于这个事实,它们在C级别上处理(请参见如何将所有变量声明为PyLongObject *类型long_pow).这也使得它们的取幂变得更加棘手繁琐,因为你需要ob_digit使用它用来表示它执行它的值的数组.(勇敢的来源. - 请参阅:了解更多关于Python中大整数的内存分配PyLongObject.)

float相反,Python 对象可以转换为C double类型(通过使用PyFloat_AsDouble),并且可以使用这些本机类型执行操作.这很好,因为在检查相关的边缘情况之后,它允许Python 使用平台'pow(C' pow,即)来处理实际的取幂:

/* 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); 
Run Code Online (Sandbox Code Playgroud)

在哪里iviw我们的原始PyFloatObjects为C doubles.

对于它的价值:Python 2.7.13对我来说是一个2~3更快的因素,并显示反向行为.

之前的事实也解释了Python 2和3之间的差异所以,我想我也会解决这个问题,因为它很有趣.

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

# Python 2
type(30)  # <type 'int'>
type(30L) # <type 'long'>
Run Code Online (Sandbox Code Playgroud)

<type 'int'>你看到这里做同样的事情float就做,它被安全地转换成C long 时,就可以进行幂(中int_pow也暗示编译器放你的歌在寄存器中,如果能够这样做,这样可以有所作为) :

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

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

要看看<type 'long'><type 'int'>s 相比有多缓慢,如果你在Python 2 xlong调用中包装了这个名字(基本上强制它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
Run Code Online (Sandbox Code Playgroud)

请注意,虽然一个片段转换为intto long而另一个片段没有(正如@pydsinger所指出的),这个演员阵容并不是减速背后的贡献力量.执行long_pow是.(仅用陈述时间陈述long(x)).

[...]它不会发生在循环之外.[...]任何想法?

这是CPython的窥视孔优化器为您折叠常量.你得到了相同的确切时间,因为没有实际的计算来找到取幂的结果,只加载值:

dis.dis(compile('4 ** 4', '', 'exec'))
  1           0 LOAD_CONST               2 (256)
              3 POP_TOP
              4 LOAD_CONST               1 (None)
              7 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

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

dis.dis(compile('4 ** 4.', '', 'exec'))
  1           0 LOAD_CONST               3 (256.0)
              2 POP_TOP
              4 LOAD_CONST               2 (None)
              6 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

所以时间是相同的.


*以上所有内容仅适用于Python的参考实现CPython.其他实现可能表现不同.

  • @ mbomb007消除Python 3中的`<type'long'>`类型可能是为了简化语言所做的努力.如果你可以有一种类型来表示整数,那么它比两种类型更容易管理(并且担心在必要时从一种转换为另一种,用户会感到困惑等等).速度增益是次要的.[PEP 237](https://www.python.org/dev/peps/pep-0237/)的基本原理部分也提供了更多见解. (3认同)

leo*_*ovp 25

如果我们查看字节码,我们可以看到表达式完全相同.唯一的区别是一个常量的类型,它将成为一个参数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
Run Code Online (Sandbox Code Playgroud)

更新:让我们看一下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()");
}
Run Code Online (Sandbox Code Playgroud)

PyNumber_Power电话ternary_op,这太长了,无法粘贴在这里,所以这里是链接.

它调用nb_power槽作为参数x传递y.

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

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);
    ...
Run Code Online (Sandbox Code Playgroud)

  • 我认为存在一种转换的含义,并且它们在线下处理不一致"最肯定"是有点延伸而没有来源. (2认同)
  • 为什么你只看那个慢的情况下不能运行`float_pow`? (2认同)
  • @ TigerhawkT3:`4**4`和`4**4.0`得到恒定折叠.这是一个完全独立的效果. (2认同)