Pourquoi est-il x**4.0
plus rapide qu'en x**4
Python 3 * ?
Les int
objets Python 3 sont un objet à part entière conçu pour prendre en charge une taille arbitraire; de ce fait, ils sont traités comme tels au niveau C (voir comment toutes les variables sont déclarées comme PyLongObject *
type in long_pow
). Cela rend également leur exponentiation beaucoup plus délicate et fastidieuse car vous devez jouer avec le ob_digit
tableau qu'il utilise pour représenter sa valeur pour l'exécuter. ( Source pour les courageux. - Voir: Comprendre l'allocation de mémoire pour les grands entiers en Python pour plus d'informations sur les PyLongObject
s.)
Les float
objets Python , au contraire, peuvent être transformés en double
type C (en utilisant PyFloat_AsDouble
) et les opérations peuvent être effectuées en utilisant ces types natifs . C'est génial car, après avoir vérifié les cas limites pertinents, cela permet à Python d' utiliser les plates-formespow
( C pow
, c'est-à-dire ) pour gérer l'exponentiation réelle:
/* 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);
où iv
et iw
sont nos originaux PyFloatObject
comme C double
s.
Pour ce que ça vaut: Python 2.7.13
pour moi est un facteur 2~3
plus rapide et montre le comportement inverse.
Le fait précédent explique aussi l'écart entre Python 2 et 3 donc, j'ai pensé que je répondrais aussi à ce commentaire car il est intéressant.
Dans Python 2, vous utilisez l'ancien int
objet qui diffère de l' int
objet dans Python 3 (tous les int
objets dans 3.x sont de PyLongObject
type). Dans Python 2, il existe une distinction qui dépend de la valeur de l'objet (ou, si vous utilisez le suffixe L/l
):
# Python 2
type(30) # <type 'int'>
type(30L) # <type 'long'>
Le <type 'int'>
voyez - vous ici fait la même chose float
ne s , il s'en toute sécurité convertie en C long
lorsque exponentiation est effectuée sur elle (le int_pow
laisse aussi le compilateur de mettre « em dans un registre si elle peut le faire, de sorte que pourrait faire une différence) :
static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */
cela permet un bon gain de vitesse.
Pour voir à quel point <type 'long'>
s est lent par rapport à <type 'int'>
s, si vous enveloppez le x
nom dans un long
appel en Python 2 (le forçant essentiellement à l'utiliser long_pow
comme dans Python 3), le gain de vitesse disparaît:
# <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
Notez que, bien que l'un des extraits de code transforme le int
en long
alors que l'autre ne le fait pas (comme l'a souligné @pydsinger), ce casting n'est pas la force contributive du ralentissement. La mise en œuvre de long_pow
est. (Chronométrez les déclarations uniquement avec long(x)
pour voir).
[...] cela ne se produit pas en dehors de la boucle. [...] Une idée à ce sujet?
Il s'agit de l'optimiseur de judas de CPython pliant les constantes pour vous. Vous obtenez le même timing exact dans les deux cas puisqu'il n'y a pas de calcul réel pour trouver le résultat de l'exponentiation, seulement le chargement des valeurs:
dis.dis(compile('4 ** 4', '', 'exec'))
1 0 LOAD_CONST 2 (256)
3 POP_TOP
4 LOAD_CONST 1 (None)
7 RETURN_VALUE
Un octet-code identique est généré pour, '4 ** 4.'
la seule différence étant que le LOAD_CONST
charge le float 256.0
au lieu de l'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
Les temps sont donc identiques.
* Tout ce qui précède s'applique uniquement à CPython, l'implémentation de référence de Python. D'autres implémentations peuvent fonctionner différemment.