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

新手上路,请多包涵

为什么 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,但如果我将 10.0 作为浮点数提高,它只是在移动24.1~4左右。

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

我注意到在这两种情况下 2 的幂都更快,我猜是因为这些计算对于解释器/计算机来说更自然/更容易。但是,有了花车,它几乎不动了。 2.0 => 24.1~4 & 128.0 => 24.1~4 但是 2 => 29 & 128 => 62


TigerhawkT3 指出它不会发生在循环之外。我检查了一下,只有在 底座 升高时才会出现这种情况(据我所见)。对此有什么想法吗?

原文由 arieljannai 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 513
2 个回答

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

Python 3 int 对象是一个完整的对象,旨在支持任意大小;由于这个事实,它们 在 C 级别上被这样处理(请参阅如何将所有变量声明为 PyLongObject * 输入 long_pow )。这也使它们的求幂变得更加 棘手 和 _乏味_,因为您需要使用 ob_digit 数组来表示它的值来执行它。 ( 勇敢者的来源。——请参阅: 了解 Python 中大整数的内存分配,了解 更多关于 PyLongObject s 的信息。)

相反,Python float 对象 可以转换 为 C double 类型(通过使用 PyFloat_AsDouble )并且可以 使用这些原生类型 执行操作。 _这很棒_,因为在检查了相关的边缘情况后,它允许 Python 使用平台的 powC 的 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);

其中 iv double iw PyFloatObject

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

之前的事实 也解释 了 Python 2 和 3 之间的差异,所以我想我也应该解决这个评论,因为它很有趣。

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

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

<type 'int'> 你在这里看到 _做同样的事情 float s 做_,它被安全地转换成 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 */

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

To see how <type 'long'> s are in comparison to <type 'int'> s, if you wrapped the x name in a long call in Python 2 (本质上是强制它使用 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) 为语句计时以查看)。

[…] 它不会发生在循环之外。 […] 对此有什么想法吗?

这是 CPython 的窥孔优化器,为您折叠常量。在任何一种情况下,您都会得到相同的精确时间,因为没有实际计算来查找求幂的结果,仅加载值:

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

Identical byte-code is generated for '4 ** 4.' with the only difference being that the LOAD_CONST loads the float 256.0 instead of the 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

所以时间是一致的。


*以上所有仅适用于 CPython,Python 的参考实现。其他实现可能会有不同的表现。

原文由 Dimitris Fasarakis Hilliard 发布,翻译遵循 CC BY-SA 3.0 许可协议

如果我们查看字节码,我们可以看到表达式完全相同。唯一的区别是常量类型将成为 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 ,太长无法粘贴到这里,所以 这里是链接

它调用 nb_power 插槽 x ,传递 y 作为参数。

最后,在 float_pow() Objects/floatobject.c 的第 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);
    ...

原文由 leovp 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题