python3 - python 3.7 download
¿Por qué es x** 4.0 más rápido que x** 4 en Python 3? (3)
¿Por qué es
x**4.0
más rápido quex**4
en Python 3 * ?
Los objetos
int
Python 3 son objetos completos diseñados para admitir un tamaño arbitrario;
debido a ese hecho, se
manejan como tales en el nivel C
(vea cómo se declaran todas las variables como tipo
PyLongObject *
en
long_pow
).
Esto también hace que su exponenciación sea mucho más
complicada
y
tediosa,
ya que debe jugar con la matriz
ob_digit
que utiliza para representar su valor para realizarla.
(
Fuente para los valientes.
- Ver:
Comprender la asignación de memoria para enteros grandes en Python
para más información sobre
PyLongObject
s.)
Los objetos
float
Python, por el contrario,
se pueden transformar
a un tipo
double
C (mediante el uso de
PyFloat_AsDouble
) y las operaciones se pueden realizar
utilizando esos tipos nativos
.
Esto es genial
porque, después de verificar los casos relevantes, le permite a Python
usar el
pow
las plataformas
(
pow
C, es decir
) para manejar la exponenciación real:
/* 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);
donde
iv
e
iw
son nuestros
PyFloatObject
s originales como C
double
s.
Para lo que vale: Python
2.7.13
para mí es un factor2~3
más rápido y muestra el comportamiento inverso.
El hecho anterior también explica la discrepancia entre Python 2 y 3, así que pensé en abordar este comentario también porque es interesante.
En Python 2, está utilizando el antiguo objeto
int
que difiere del objeto
int
en Python 3 (todos los objetos
int
en 3.x son del tipo
PyLongObject
).
En Python 2, hay una distinción que depende del valor del objeto (o, si usa el sufijo
L/l
):
# Python 2
type(30) # <type ''int''>
type(30L) # <type ''long''>
El
<type ''int''>
que ves aquí
hace lo mismo que
float
, se convierte de forma segura en una C
long
cuando se realiza la exponenciación en él
(
int_pow
también sugiere al compilador que los ponga en un registro si puede hacerlo entonces, eso
podría
hacer la diferencia):
static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */
Esto permite una buena ganancia de velocidad.
Para ver cuán lentos son
<type ''long''>
s en comparación con
<type ''int''>
s, si envuelve el nombre
x
en una llamada
long
en Python 2 (esencialmente forzándolo a usar
long_pow
como en Python 3), el la ganancia de velocidad desaparece:
# <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
Tenga en cuenta que, aunque un fragmento transforma el
int
en
long
mientras que el otro no (como lo señaló @pydsinger), este lanzamiento no es la fuerza que contribuye a la desaceleración.
La implementación de
long_pow
es.
(Mida las declaraciones únicamente con
long(x)
para ver).
[...] no sucede fuera del circuito. [...] ¿Alguna idea sobre eso?
Este es el optimizador de mirilla de CPython que dobla las constantes por usted. Obtiene los mismos tiempos exactos en cualquier caso, ya que no hay un cálculo real para encontrar el resultado de la exponenciación, solo carga de valores:
dis.dis(compile(''4 ** 4'', '''', ''exec''))
1 0 LOAD_CONST 2 (256)
3 POP_TOP
4 LOAD_CONST 1 (None)
7 RETURN_VALUE
Se genera un código de bytes idéntico para
''4 ** 4.''
con la única diferencia de que
LOAD_CONST
carga el float
256.0
lugar del 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
Entonces los tiempos son idénticos.
* Todo lo anterior se aplica únicamente a CPython, la implementación de referencia de Python. Otras implementaciones pueden funcionar de manera diferente.
¿Por qué es
x**4.0
más rápido que
x**4
?
Estoy usando 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
Intenté cambiar la potencia con la que aumenté para ver cómo actúa, y por ejemplo, si elevo x a la potencia de 10 o 16, está saltando de 30 a 35, pero si estoy aumentando 10.0 como flotador, solo se mueve alrededor de 24.1 ~ 4.
Supongo que tiene algo que ver con la conversión de flotación y potencias de 2 tal vez, pero realmente no lo sé.
Noté que en ambos casos las potencias de 2 son más rápidas, supongo que dado que esos cálculos son más nativos / fáciles para el intérprete / computadora.
Pero aún así, con las carrozas casi no se mueve.
2.0 => 24.1~4 & 128.0 => 24.1~4
pero
2 => 29 & 128 => 62
TigerhawkT3 señaló que no sucede fuera del circuito. Verifiqué y la situación solo ocurre (por lo que he visto) cuando la base se está elevando. ¿Alguna idea sobre eso?
Porque uno es correcto, otro es aproximación.
>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625
Si miramos el código de bytes, podemos ver que las expresiones son puramente idénticas.
La única diferencia es un tipo de constante que será un argumento de
BINARY_POWER
.
Entonces, ciertamente se debe a que un
int
se convierte en un número de coma flotante en la línea.
>>> 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
Actualización: echemos un vistazo a Objects/abstract.c en el código fuente de CPython:
PyObject *
PyNumber_Power(PyObject *v, PyObject *w, PyObject *z)
{
return ternary_op(v, w, z, NB_SLOT(nb_power), "** or pow()");
}
PyNumber_Power
llama a
ternary_op
, que es demasiado largo para pegar aquí, así que
aquí está el enlace
.
Llama a la ranura
nb_power
de
x
, pasando
y
como argumento.
Finalmente, en
float_pow()
en la línea 686 de
Objects/floatobject.c
vemos que los argumentos se convierten en un
double
C justo antes de la operación real:
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);
...