tutorial python3 org learn documentacion docs python performance python-3.x python-3.5 python-internals

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 que x**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 factor 2~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); ...