2015-07-21 10 views
31

Ho cercato di capire perché Python 3 in realtà richiede molto tempo rispetto a Python 2 in alcune situazioni, di seguito sono pochi i casi che ho verificato da Python 3.4 a python 2.7.Perché Python 3 è notevolmente più lento di Python 2?

Nota: ho risposto ad alcune domande come Why is there no xrange function in Python3? e loop in python3 much slower than python2 e Same code slower in Python3 as compared to Python2, ma ritengo di non aver ottenuto il vero motivo alla base di questo problema.

Ho provato questo pezzo di codice per mostrare come si sta facendo la differenza:

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 

Quando ho provato a fare funzionare questo programma con py3.4 e py2.7 ho seguito risultati .

Nota: queste statistiche sono passate attraverso una macchina 64 bit con processore 2.6Ghz e hanno calcolato l'ora utilizzando time.time() in un unico ciclo.

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

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

Io davvero non credo che ci sia stata modifiche applicate ai while o xrange 2,7-3,4, so range è stato avviato in qualità di xrange in py3.4 ma come documentazione dice

range() ora si comporta come xrange() utilizzato per comportarsi, tranne che funziona con valori di dimensione arbitraria. Quest'ultimo non esiste più.

questo significa cambiamento da xrange a range è molto pari ad una variazione nome ma lavorare con valori arbitrari.

Ho verificato anche il codice byte disassemblato.

Di seguito si riporta il codice di byte smontato per la funzione 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   

E sotto è il codice di byte smontato per la funzione 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   

Se confrontiamo entrambi i codici di byte hanno prodotto lo stesso codice byte disassemblato.

Ora mi chiedo quale cambiamento dal 2.7 al 3.4 stia davvero causando questo enorme cambiamento nei tempi di esecuzione nel dato pezzo di codice.

+1

inserisci il codice completo e il metodo di misurazione – njzk2

+3

L'esecuzione di questa operazione solo una volta con * l'intera configurazione dell'interprete * non ti dirà nulla. Usa 'timeit.timeit()' per eseguire invece le prove a tempo. –

risposta

26

La differenza è nell'implementazione del tipo int. Python 3.x utilizza esclusivamente il tipo di intero di dimensione arbitraria (long in 2.x), mentre in Python 2.x per valori fino a sys.maxint viene utilizzato un tipo più semplice int che utilizza un semplice C long sotto il cofano.

Una volta che si limitano i loop a long numeri interi, Python 3.x è più veloce:

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

ho usato sys.maxsize come sys.maxint era caduto da Python 3, ma il valore intero è fondamentalmente la stessa .

La differenza di velocità in Python 2 è quindi limitata al primo (2 ** 63) - 1 numeri interi su 64 bit, (2 ** 31) - 1 numeri interi su sistemi a 32 bit.

Poiché non è possibile utilizzare il tipo long con xrange() in Python 2, non ho incluso un confronto per tale funzione.

+7

C'è qualche ragione per cui non si desidera ottimizzare i numeri interi al di sotto di 2 ** 63? Sembrano essere i più usati ... – thebjorn

+0

@thebjorn: la semplificazione dell'uso di un tipo 'int' era più importante. Inoltre, se stai facendo cicli 'for' su un intervallo così ampio, probabilmente stai facendo qualcosa di sbagliato * comunque *. –

+4

Ma questa scelta non effettua tutti i calcoli integer su es. anche gli indici di array, ecc. Sembra che altri linguaggi (Smalltalk, Lisp, Haskell, Java) vadano a un certo punto per ottimizzare il boxing/unboxing degli interi, queste ottimizzazioni sono superflue in un linguaggio come Python? – thebjorn