web-dev-qa-db-fra.com

Pourquoi Python 3 est considérablement plus lent que Python 2?

J'ai essayé de comprendre pourquoi Python 3 prend en fait beaucoup de temps par rapport à Python 2 dans certaines situations, voici quelques cas que j'ai vérifiés à partir de python 3.4 à python 2.7.

Remarque: j'ai parcouru certaines des questions comme Pourquoi n'y a-t-il pas de fonction xrange en Python3? et boucle en python3 beaucoup plus lente que python2 et Même code plus lent dans Python3 par rapport à Python2 , mais je pense que je n'ai pas eu la véritable raison derrière ce problème.

J'ai essayé ce morceau de code pour montrer comment cela fait la différence:

MAX_NUM = 3*10**7

# This is to make compatible with py3.4.
try:
    xrange
except:
    xrange = range


def foo():
    i = MAX_NUM
    while i> 0:
        i -= 1

def foo_for():
    for i in xrange(MAX_NUM):
        pass

Lorsque j'ai essayé d'exécuter ce programme avec py3.4 et py2.7, j'ai les résultats ci-dessous.

Remarque: Ces statistiques proviennent d'une machine 64 bit Avec un processeur 2.6Ghz Et ont calculé le temps en utilisant time.time() en boucle unique.

Output : Python 3.4
-----------------
2.6392083168029785
0.9724123477935791

Output: Python 2.7
------------------
1.5131521225
0.475143909454

Je ne pense vraiment pas qu'il y ait eu des changements appliqués à while ou xrange de 2.7 à 3.4, je sais que range a commencé à agir comme à xrange dans py3.4 mais comme le dit la documentation

range() se comporte désormais comme xrange() se comportait autrefois, sauf qu'il fonctionne avec des valeurs de taille arbitraire. Ce dernier n'existe plus.

cela signifie que le changement de xrange en range est très similaire à un changement de nom mais fonctionne avec des valeurs arbitraires.

J'ai également vérifié le code d'octet démonté.

Ci-dessous se trouve le code d'octet démonté pour la fonction foo():

Python 3.4:
--------------- 

 13           0 LOAD_GLOBAL              0 (MAX_NUM)
              3 STORE_FAST               0 (i)

 14           6 SETUP_LOOP              26 (to 35)
        >>    9 LOAD_FAST                0 (i)
             12 LOAD_CONST               1 (0)
             15 COMPARE_OP               4 (>)
             18 POP_JUMP_IF_FALSE       34

 15          21 LOAD_FAST                0 (i)
             24 LOAD_CONST               2 (1)
             27 INPLACE_SUBTRACT
             28 STORE_FAST               0 (i)
             31 JUMP_ABSOLUTE            9
        >>   34 POP_BLOCK
        >>   35 LOAD_CONST               0 (None)
             38 RETURN_VALUE

python 2.7
-------------

 13           0 LOAD_GLOBAL              0 (MAX_NUM)
              3 STORE_FAST               0 (i)

 14           6 SETUP_LOOP              26 (to 35)
        >>    9 LOAD_FAST                0 (i)
             12 LOAD_CONST               1 (0)
             15 COMPARE_OP               4 (>)
             18 POP_JUMP_IF_FALSE       34

 15          21 LOAD_FAST                0 (i)
             24 LOAD_CONST               2 (1)
             27 INPLACE_SUBTRACT    
             28 STORE_FAST               0 (i)
             31 JUMP_ABSOLUTE            9
        >>   34 POP_BLOCK           
        >>   35 LOAD_CONST               0 (None)
             38 RETURN_VALUE        

Et ci-dessous est le code d'octet démonté pour la fonction foo_for():

Python: 3.4

 19           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_GLOBAL              1 (MAX_NUM)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 GET_ITER
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

 20          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE


Python: 2.7
-------------

 19           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_GLOBAL              1 (MAX_NUM)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

 20          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK           
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        

Si nous comparons les deux codes d'octets, ils ont produit le même code d'octets démonté.

Maintenant, je me demande quel changement de 2.7 à 3.4 est vraiment à l'origine de cet énorme changement dans le temps d'exécution dans le morceau de code donné.

36
gsb-eng

La différence réside dans l'implémentation du type int. Python 3.x utilise exclusivement le type entier de taille arbitraire (long dans 2.x), tandis que dans Python 2.x pour valeurs jusqu'à sys.maxint un type int plus simple est utilisé qui utilise un simple C long sous le capot.

Une fois que vous avez limité vos boucles à long entiers, Python 3.x est plus rapide:

>>> from timeit import timeit
>>> MAX_NUM = 3*10**3
>>> def bar():
...     i = MAX_NUM + sys.maxsize
...     while i > sys.maxsize:
...         i -= 1
... 

Python 2:

>>> timeit(bar, number=10000)
5.704327821731567

Python 3:

>>> timeit(bar, number=10000)
3.7299320790334605

J'ai utilisé sys.maxsize Car sys.maxint A été supprimé de Python 3, mais la valeur entière est fondamentalement la même.

La différence de vitesse dans Python 2 est donc limitée aux premiers (2 ** 63) - 1 entier sur 64 bits, (2 ** 31) - 1 entier sur les systèmes 32 bits.

Comme vous ne pouvez pas utiliser le type long avec xrange() on Python 2, je n'ai pas inclus de comparaison pour cette fonction.

34
Martijn Pieters