为什么 x**4.0 在 Python 3 中比 x**4 快?
Why is x**4.0 faster than x**4 in Python 3?
为什么 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 的幂都更快,我猜是因为 interpreter/computer 的这些计算更多 native/easy。但是,有了花车,它几乎不动了。 2.0 => 24.1~4 & 128.0 => 24.1~4
但是2 => 29 & 128 => 62
TigerhawkT3 指出它不会发生在循环之外。我检查了一下,只有在 base 被提升时才会出现这种情况(据我所见)。对此有什么想法吗?
如果我们查看字节码,我们会发现表达式完全相同。唯一的区别是常量的类型将成为 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
,太长了无法粘贴到这里,所以 here's the link.
它调用 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);
...
Why is x**4.0
faster than x**4
in Python 3*?
Python 3 int
对象是一个完整的对象,旨在支持任意大小;由于这个事实,他们 handled as such on the C level (see how all variables are declared as PyLongObject *
type in long_pow
). This also makes their exponentiation a lot more trickier and tedious since you need to play around with the ob_digit
array it uses to represent its value to perform it. (Source for the brave. -- See: 更多关于 PyLongObject
s。)
Python float
对象,相反,可以转换为C double
类型(通过使用PyFloat_AsDouble
) and operations can be performed using those native types. This is great because, after checking for relevant edge-cases, it allows Python to 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
是我们原来的 PyFloatObject
s 作为 C double
s.
For what it's worth: Python 2.7.13
for me is a factor 2~3
faster, and shows the inverse behaviour.
前面的事实 也解释了 Python 2 和 3 之间的差异所以,我想我也应该解决这个评论,因为它很有趣。
在 Python 2 中,您使用的是与 Python 3 中的 int
对象不同的旧 int
对象(所有 int
对象在 3.x 中属于 PyLongObject
类型)。在Python 2中,有一个区别取决于对象的值(或者,如果使用后缀L/l
):
# Python 2
type(30) # <type 'int'>
type(30L) # <type 'long'>
你在这里看到的 <type 'int'>
做同样的事情 float
s 做,它被安全地转换成 C long
when 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 相比有多慢,如果您将 x
名称包装在 Python 中的 long
调用中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)
对语句计时即可查看)。
[...] it doesn't happen outside of the loop. [...] Any idea about that?
这是 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.'
生成相同的 byte-code,唯一的区别是 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
所以时间是相同的。
*以上所有仅适用于 CPython,Python 的参考实现。其他实现可能会有所不同。
因为一个是正确的,另一个是近似值。
>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625
为什么 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 的幂都更快,我猜是因为 interpreter/computer 的这些计算更多 native/easy。但是,有了花车,它几乎不动了。 2.0 => 24.1~4 & 128.0 => 24.1~4
但是2 => 29 & 128 => 62
TigerhawkT3 指出它不会发生在循环之外。我检查了一下,只有在 base 被提升时才会出现这种情况(据我所见)。对此有什么想法吗?
如果我们查看字节码,我们会发现表达式完全相同。唯一的区别是常量的类型将成为 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
,太长了无法粘贴到这里,所以 here's the link.
它调用 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);
...
Why is
x**4.0
faster thanx**4
in Python 3*?
Python 3 int
对象是一个完整的对象,旨在支持任意大小;由于这个事实,他们 handled as such on the C level (see how all variables are declared as PyLongObject *
type in long_pow
). This also makes their exponentiation a lot more trickier and tedious since you need to play around with the ob_digit
array it uses to represent its value to perform it. (Source for the brave. -- See: PyLongObject
s。)
Python float
对象,相反,可以转换为C double
类型(通过使用PyFloat_AsDouble
) and operations can be performed using those native types. This is great because, after checking for relevant edge-cases, it allows Python to 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
是我们原来的 PyFloatObject
s 作为 C double
s.
For what it's worth: Python
2.7.13
for me is a factor2~3
faster, and shows the inverse behaviour.
前面的事实 也解释了 Python 2 和 3 之间的差异所以,我想我也应该解决这个评论,因为它很有趣。
在 Python 2 中,您使用的是与 Python 3 中的 int
对象不同的旧 int
对象(所有 int
对象在 3.x 中属于 PyLongObject
类型)。在Python 2中,有一个区别取决于对象的值(或者,如果使用后缀L/l
):
# Python 2
type(30) # <type 'int'>
type(30L) # <type 'long'>
你在这里看到的 <type 'int'>
做同样的事情 float
s 做,它被安全地转换成 C long
when 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 相比有多慢,如果您将 x
名称包装在 Python 中的 long
调用中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)
对语句计时即可查看)。
[...] it doesn't happen outside of the loop. [...] Any idea about that?
这是 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.'
生成相同的 byte-code,唯一的区别是 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
所以时间是相同的。
*以上所有仅适用于 CPython,Python 的参考实现。其他实现可能会有所不同。
因为一个是正确的,另一个是近似值。
>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625