为什么在Python 3中x ** 4.0比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指出,它不会发生在循环之外。 我查了一下,只有当基地升起的时候,情况才发生(从我看到的情况)。 任何想法呢?

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

Python 3的int对象是一个完整的对象,旨在支持任意大小; 由于这个事实,它们在C级别被处理 (查看所有variables在long_pow中声明为PyLongObject *types)。 这也使得它们的指数变得更加棘手乏味,因为你需要使用ob_digit数组来表示它的值来执行它。 ( 勇敢的源代码 – 请参阅: 了解Python中的大整数的内存分配,了解 PyLongObject的更多内容。

相反,Python float对象可以转换为C doubletypes(通过使用PyFloat_AsDouble ),并且可以使用这些本地types执行操作。 这很好,因为在检查相关的边界情况之后,它允许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); 

其中iviw是我们原来的PyFloatObject s的C double s。

值得一提的是:Python 2.7.13对于我来说是一个快2~3倍的因素,并且显示了逆行。

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

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

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

你在这里看到的<type 'int'> float s做的是一样的 , 当对它进行指数运算时 ,它会被安全地转换成一个C longint_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'> long_pow与< long_pow 's <type 'int'>相比是多么缓慢,如果你在Python 2中调用了一个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 

请注意,尽pipe这个片段将int转换为long int ,而另一片却没有(正如@pydsinger所指出的那样),但这个演员并不是减速背后的推动力量。 long_pow的实现是。 (时间单独用long(x)来看)。

它不会发生在循环之外。 对此有任何想法?

这是CPython的窥视优化器为您折叠的常量。 由于没有实际的计算来find指数运算的结果,所以只能加载值:

 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.'生成相同的字节码'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。 其他实现可能会有所不同。

如果我们看字节码,我们可以看到expression式是完全相同的。 唯一的区别是一个常量的types,将是一个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 ,这太粘贴了,所以这里是链接 。

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

最后,在Objects / floatobject.c的第686行的float_pow() ,我们看到参数在实际操作之前转换为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); ... 

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

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