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

为什么 x**4.0x**4快? 我用的是 CPython3.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


虎鹰 T3指出它不会发生在循环之外。我检查和情况只发生(从我所看到的)时,基地正在得到提高。有什么想法吗?

16658 次浏览

如果我们查看字节码,我们可以看到表达式是完全相同的。唯一的区别是一个常量的类型,它将是 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 源代码中的 对象/抽象:

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作为参数传递。

最后,在 对象/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);
...

为什么在 Python 3*x**4.0 再快点x**4好?

Python 3 int对象是一个完整的对象,设计用于支持任意大小; 因此,它们是 在 C 级也是这样处理的(请参见如何在 long_pow中将所有变量声明为 PyLongObject *类型)。这也使得它们的幂运算更多的是 更棘手乏味,因为您需要使用它用来表示值的 ob_digit数组来执行它。(有关 PyLongObject的更多信息,请参见: 了解 Python 中大型整数的内存分配。)

Python float对象恰恰相反,可以被改变为 C double类型(通过使用 PyFloat_AsDouble) ,操作可以执行 使用那些土著人。因为在检查相关的边界情况之后,它允许 Python 使用 使用平台的 pow(double0)来处理实际的幂运算:

/* 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是我们的原始 PyFloatObject作为 Cdouble

值得一提的是: Python2.7.13对我来说比 2~3快一个因子,并且显示了相反的行为。

前面的事实 也解释了是 Python2和 Python3之间的差异,所以我想我也应该解决这个问题,因为它很有趣。

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

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

你在这里看到的 <type 'int'>,它被安全地转换成了 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 */

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

为了看看 <type 'long'><type 'int'>相比有多慢,如果你在 Python 2中的 long调用中包装了 x的名字(实际上强迫它像 Python 3一样使用 long_pow) ,速度增益就消失了:

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

'4 ** 4.'生成相同的字节码,唯一的区别是 LOAD_CONST加载浮点 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

所以时间是一样的。


* 以上所有内容只适用于 Python 的参考实现 CPython,其他实现的性能可能有所不同。

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

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