2009-05-15 2 views
56

L'altro giorno stavo facendo un po 'di benchmark Python e mi sono imbattuto in qualcosa di interessante. Di seguito sono riportati due cicli che fanno più o meno la stessa cosa. Il Loop 1 impiega circa il doppio del loop 2 da eseguire.Perché il loop su range() in Python è più veloce rispetto all'utilizzo di un ciclo while?

Loop 1:

int i = 0 
while i < 100000000: 
    i += 1 

Loop 2:

for n in range(0,100000000): 
    pass 

Perché il primo ciclo in modo molto più lento? So che è un esempio banale, ma ha suscitato il mio interesse. C'è qualcosa di speciale nella funzione range() che lo rende più efficiente dell'incremento di una variabile nello stesso modo?

risposta

113

vedere il disassemblaggio del codice Python di byte, si può avere un'idea più concreta

uso ciclo while:

1   0 LOAD_CONST    0 (0) 
      3 STORE_NAME    0 (i) 

2   6 SETUP_LOOP    28 (to 37) 
     >> 9 LOAD_NAME    0 (i)    # <- 
      12 LOAD_CONST    1 (100000000)  # <- 
      15 COMPARE_OP    0 (<)    # <- 
      18 JUMP_IF_FALSE   14 (to 35)   # <- 
      21 POP_TOP          # <- 

3   22 LOAD_NAME    0 (i)    # <- 
      25 LOAD_CONST    2 (1)    # <- 
      28 INPLACE_ADD         # <- 
      29 STORE_NAME    0 (i)    # <- 
      32 JUMP_ABSOLUTE   9     # <- 
     >> 35 POP_TOP 
      36 POP_BLOCK 

Il corpo del ciclo ha 10 op

range di utilizzo:

1   0 SETUP_LOOP    23 (to 26) 
      3 LOAD_NAME    0 (range) 
      6 LOAD_CONST    0 (0) 
      9 LOAD_CONST    1 (100000000) 
      12 CALL_FUNCTION   2 
      15 GET_ITER 
     >> 16 FOR_ITER     6 (to 25)  # <- 
      19 STORE_NAME    1 (n)   # <- 

2   22 JUMP_ABSOLUTE   16    # <- 
     >> 25 POP_BLOCK 
     >> 26 LOAD_CONST    2 (None) 
      29 RETURN_VALUE 

Il corpo del circuito ha 3 op

Il tempo di eseguire il codice C è molto più breve dell'interprete e può essere ignorato.

+32

+1 per spiegare una risposta con uno smontaggio – TwentyMiles

+2

In realtà il corpo del ciclo nel primo smontaggio ha 10 operazioni (il salto dalla posizione 32 alla 9).Nell'implementazione corrente di CPython, l'interpretazione di ogni bytecode si traduce in una probabilità piuttosto elevata in una costosa appropriazione indebita di rami indiretti nella CPU (il passaggio all'implementazione del bytecode successivo). Questa è una conseguenza dell'attuale implementazione di CPython, le JIT implementate da swallow a vuoto, PyPy e altre molto probabilmente perderanno questo overhead. Il meglio di loro sarà anche in grado di fare specializzazione di tipo per un aumento della velocità dell'ordine. –

+2

+1 - @kcwu: come lo hai smontato? –

3

Perché si sta eseguendo più spesso nel codice scritto in C nell'interprete. cioè i + = 1 è in Python, quindi lento (comparativamente), mentre range (0, ...) è un C call che for si eseguirà per lo più anche in C.

29

range() è implementato in C, mentre viene interpretato i += 1.

Utilizzare xrange() potrebbe renderlo ancora più veloce per i grandi numeri. A partire da Python 3.0 range() è lo stesso del precedente xrange().

2

La maggior parte delle chiamate di metodo incorporate in Python vengono eseguite come codice C. Il codice che deve essere interpretato è molto più lento. In termini di efficienza della memoria e velocità di esecuzione, la differenza è enorme. Gli interni di Python sono stati ottimizzati all'estremo, ed è meglio approfittare di tali ottimizzazioni.

11

Va detto che c'è un sacco di creazione di oggetti e distruzione in corso con il ciclo while.

i += 1 

è uguale a:

i = i + 1 

Ma poiché interi Python sono immutabili, non modifica l'oggetto esistente; piuttosto crea un oggetto nuovo di zecca con un nuovo valore. È fondamentalmente:

i = new int(i + 1) # Using C++ or Java-ish syntax 

Il garbage collector avrà anche una grande quantità di pulizia da fare. "La creazione dell'oggetto è costosa".