为什么在 Python 3 中x**4.0 比x**4 更快*?
Python 3 int 对象是一个成熟的对象,旨在支持任意大小;由于这个事实,它们是handled as such on the C level(请参阅long_pow 中的所有变量如何声明为PyLongObject * 类型)。这也使得它们的求幂变得更加更复杂和乏味,因为您需要使用 ob_digit 数组来表示其执行它的值。 (Source for the brave. -- 有关PyLongObjects 的更多信息,请参阅:Understanding memory allocation for large integers in Python。)
Python float 对象,相反,可以转换为 C double 类型(通过使用PyFloat_AsDouble)并且可以执行操作using those native types。 这太棒了,因为在检查了相关的边缘情况后,它允许 Python use the platforms' pow (C's pow, that is) 处理实际的求幂:
/* 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 是我们原来的PyFloatObjects 作为C doubles。
对于它的价值:Python 2.7.13 对我来说是一个因素 2~3 更快,并显示相反的行为。
前面的事实也解释了 Python 2 和 3 之间的差异,所以我想我也应该解决这个评论,因为它很有趣。
在 Python 2 中,您使用的旧 int 对象与 Python 3 中的 int 对象不同(3.x 中的所有 int 对象都是 PyLongObject 类型)。在 Python 2 中,有一个区别取决于对象的值(或者,如果您使用后缀 L/l):
# Python 2
type(30) # <type 'int'>
type(30L) # <type 'long'>
你在这里看到的<type 'int'>做同样的事情floats 做,它被安全地转换为 C longwhen exponentiation is performed on it(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'>s 与<type 'int'>s 相比有多迟钝,如果您在 Python 2 中将 x 名称包装在 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
请注意,虽然一个 sn-p 将 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 加载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
所以时间是相同的。
*以上所有内容仅适用于 CPython,即 Python 的参考实现。其他实现可能会有不同的表现。