Pourquoi x ** 4.0 est-il plus rapide que x ** 4 dans Python 3?

Pourquoi x**4.0 plus rapide que x**4 ? J’utilise 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 

J’ai essayé de changer la puissance que j’ai relevée pour voir comment ça se passe, et par exemple si je relance x à la puissance de 10 ou 16, il passe de 30 à 35, mais si je relance de 10.0 , ça bouge autour de 24,1 ~ 4.

Je suppose que cela a quelque chose à voir avec la conversion des flotteurs et les puissances de 2, mais je ne sais pas vraiment.

Je suppose que dans les deux cas, les puissances de 2 sont plus rapides, puisque ces calculs sont plus natifs / faciles pour l’interprète / l’ordinateur. Mais quand même, avec les flotteurs, ça ne bouge presque pas. 2.0 => 24.1~4 & 128.0 => 24.1~4 mais 2 => 29 & 128 => 62


TigerhawkT3 a souligné que cela ne se produit pas en dehors de la boucle. J’ai vérifié et la situation ne se produit que (d’après ce que j’ai vu) lorsque la base est soulevée. Une idée à ce sujet?

    Pourquoi x**4.0 plus rapide que x**4 dans Python 3 * ?

    Les objects Python 3 int sont un object à 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 en tant PyLongObject * type PyLongObject * dans long_pow ). Cela rend également leur exponentiation beaucoup plus compliquée et fastidieuse puisque vous devez jouer avec le tableau ob_digit utilisé pour représenter sa valeur. ( Source pour les plus courageux. – Voir: Comprendre l’allocation de mémoire pour les grands entiers en Python pour plus d’informations sur PyLongObject s.)

    Les objects Python float , au contraire, peuvent être transformés en un type C double (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 bordures pertinentes, Python peut utiliser le pow la plate-forme ( le pow de C, 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); 

    iv et iw sont nos originaux PyFloatObject s en 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 la différence entre Python 2 et 3, donc j’ai pensé aborder ce commentaire aussi parce que c’est intéressant.

    Dans Python 2, vous utilisez l’ancien object int qui diffère de l’object int de Python 3 (tous les objects int de 3.x sont de type PyLongObject ). En Python 2, il y a une distinction qui dépend de la valeur de l’object (ou, si vous utilisez le suffixe L/l ):

     # Python 2 type(30) #  type(30L) #  

    Le vous voyez ici fait la même chose que le float s , il est converti en C long en toute sécurité quand une exponentiation est effectuée dessus ( int_pow suggère également au compilateur de les placer dans un registre s’il peut le faire) alors, ça pourrait faire la 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 s est comparé à s, si vous avez enveloppé le nom x dans un long appel dans Python 2 (le forçant essentiellement à utiliser long_pow comme dans Python 3), le gain de vitesse disparaît:

     #  (python2) ➜ python -m timeit "for x in range(1000):" " x**2" 10000 loops, best of 3: 116 usec per loop #  (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 transforme l’ int à long que l’autre ne le fasse pas (comme le fait remarquer @pydsinger), cette dissortingbution n’est pas la force consortingbuant au ralentissement. L’implémentation de long_pow est. (Chronométrez les instructions uniquement avec long(x) à voir).

    […] cela ne se produit pas en dehors de la boucle. […] Une idée à ce sujet?

    Ceci est l’optimiseur de peephole de CPython en pliant les constantes pour vous. Vous obtenez les mêmes timings exacts dans les deux cas, car il n’y a pas de calcul réel pour trouver le résultat de l’exponentiation, uniquement le chargement des valeurs:

     dis.dis(comstack('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 LOAD_CONST charge le float 256.0 au lieu de 256 :

     dis.dis(comstack('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.

    Si nous regardons le bytecode, nous pouvons voir que les expressions sont purement identiques. La seule différence est un type de constante qui sera un argument de BINARY_POWER . Il est donc très certainement dû à la conversion d’un int en nombre à virgule flottante.

     >>> 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 

    Mise à jour: jetons un coup d’œil à Objects / abstract.c dans le code source 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 appelle ternary_op , qui est trop long pour être collé ici, alors voici le lien .

    Il appelle l’emplacement nb_power de x , en passant y comme argument.

    Enfin, dans float_pow() à la ligne 686 de Objects / floatobject.c, nous voyons que les arguments sont convertis en double C juste avant l’opération réelle:

     static PyObject * float_pow(PyObject *v, PyObject *w, PyObject *z) { double iv, iw, ix; int negate_result = 0; if ((PyObject *)z != Py_None) { PyErr_SetSsortingng(PyExc_TypeError, "pow() 3rd argument not " "allowed unless all arguments are integers"); return NULL; } CONVERT_TO_DOUBLE(v, iv); CONVERT_TO_DOUBLE(w, iw); ... 

    Parce que l’un est correct, un autre est l’approximation.

     >>> 334453647687345435634784453567231654765 ** 4.0 1.2512490121794596e+154 >>> 334453647687345435634784453567231654765 ** 4 125124901217945966595797084130108863452053981325370920366144 719991392270482919860036990488994139314813986665699000071678 41534843695972182197917378267300625