为什么在Python 3中x ** 4.0比x ** 4更快?
为什么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
我尝试改变我提出的权力,看看它是如何行动的,例如,如果我把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 double
types(通过使用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);
其中iv
和iw
是我们原来的PyFloatObject
s的C double
s。
值得一提的是:Python
2.7.13
对于我来说是一个快2~3
倍的因素,并且显示了逆行。
前面的事实也解释了Python 2和Python 3之间的差异,所以我想我也会解释这个评论,因为它很有趣。
在Python 2中,您使用的是与Python 3中的int
对象不同的旧int
对象(3.x中的所有int
对象都是PyLongObject
types)。 在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 */
这允许一个很好的速度增益。
为了看看<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
,这太粘贴了,所以这里是链接 。
它调用x
的nb_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