79 votes

Python : pourquoi * et ** sont-ils plus rapides que / et sqrt() ?

En optimisant mon code, je me suis rendu compte de ce qui suit :

>>> from timeit import Timer as T
>>> T(lambda : 1234567890 / 4.0).repeat()
[0.22256922721862793, 0.20560789108276367, 0.20530295372009277]
>>> from __future__ import division
>>> T(lambda : 1234567890 / 4).repeat()
[0.14969301223754883, 0.14155197143554688, 0.14141488075256348]
>>> T(lambda : 1234567890 * 0.25).repeat()
[0.13619112968444824, 0.1281130313873291, 0.12830305099487305]

et aussi :

>>> from math import sqrt
>>> T(lambda : sqrt(1234567890)).repeat()
[0.2597470283508301, 0.2498021125793457, 0.24994492530822754]
>>> T(lambda : 1234567890 ** 0.5).repeat()
[0.15409398078918457, 0.14059877395629883, 0.14049601554870605]

Je suppose que cela a à voir avec la façon dont Python est implémenté en C, mais je me demande si quelqu'un pourrait expliquer pourquoi il en est ainsi.

113voto

NPE Points 169956

La raison (quelque peu inattendue) de vos résultats est que Python semble plier les expressions constantes impliquant la multiplication et l'exponentiation en virgule flottante, mais pas la division. math.sqrt() est tout à fait différent puisqu'il n'y a pas de bytecode pour lui et qu'il implique un appel de fonction.

Sur Python 2.6.5, le code suivant :

x1 = 1234567890.0 / 4.0
x2 = 1234567890.0 * 0.25
x3 = 1234567890.0 ** 0.5
x4 = math.sqrt(1234567890.0)

se compile avec les bytecodes suivants :

  # x1 = 1234567890.0 / 4.0
  4           0 LOAD_CONST               1 (1234567890.0)
              3 LOAD_CONST               2 (4.0)
              6 BINARY_DIVIDE       
              7 STORE_FAST               0 (x1)

  # x2 = 1234567890.0 * 0.25
  5          10 LOAD_CONST               5 (308641972.5)
             13 STORE_FAST               1 (x2)

  # x3 = 1234567890.0 ** 0.5
  6          16 LOAD_CONST               6 (35136.418286444619)
             19 STORE_FAST               2 (x3)

  # x4 = math.sqrt(1234567890.0)
  7          22 LOAD_GLOBAL              0 (math)
             25 LOAD_ATTR                1 (sqrt)
             28 LOAD_CONST               1 (1234567890.0)
             31 CALL_FUNCTION            1
             34 STORE_FAST               3 (x4)

Comme vous pouvez le constater, la multiplication et l'exponentiation ne prennent pas de temps puisqu'elles sont effectuées lors de la compilation du code. La division prend plus de temps puisqu'elle se fait au moment de l'exécution. La racine carrée n'est pas seulement l'opération la plus coûteuse des quatre sur le plan du calcul, elle entraîne également divers frais généraux que les autres n'ont pas à supporter (recherche d'attributs, appel de fonctions, etc.).

Si l'on élimine l'effet du pliage constant, il n'y a pas grand-chose qui sépare la multiplication de la division :

In [16]: x = 1234567890.0

In [17]: %timeit x / 4.0
10000000 loops, best of 3: 87.8 ns per loop

In [18]: %timeit x * 0.25
10000000 loops, best of 3: 91.6 ns per loop

math.sqrt(x) est en fait un peu plus rapide que x ** 0.5 probablement parce qu'il s'agit d'un cas particulier de ce dernier et qu'il peut donc être réalisé plus efficacement, malgré les frais généraux :

In [19]: %timeit x ** 0.5
1000000 loops, best of 3: 211 ns per loop

In [20]: %timeit math.sqrt(x)
10000000 loops, best of 3: 181 ns per loop

modifier 2011-11-16 : Le pliage des expressions constantes est effectué par l'optimiseur peephole de Python. Le code source ( peephole.c ) contient le commentaire suivant qui explique pourquoi la division constante n'est pas pliée :

    case BINARY_DIVIDE:
        /* Cannot fold this operation statically since
           the result can depend on the run-time presence
           of the -Qnew flag */
        return 0;

En -Qnew permet d'activer la "vraie division" définie dans PEP 238 .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X