2010-04-10 11 views
11

Qual è la logica alla base dell'uso consigliato dei costrutti di ciclo for i in xrange(...) in Python? Per il ciclo intero semplice, la differenza nei costi generali è notevole. Ho condotto un test semplice utilizzando due pezzi di codice:Razionale dietro la sintassi preferita da Python

file idiomatic.py:

#!/usr/bin/env python 

M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    for x in xrange(N): 
     for y in xrange(M): 
      pass 

File cstyle.py:

#!/usr/bin/env python 

M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    while x < N: 
     while y < M: 
      y += 1 
     x += 1 

profilatura risultati sono stati i seguenti:

bash-3.1$ time python cstyle.py 

real 0m0.109s 
user 0m0.015s 
sys  0m0.000s 

bash-3.1$ time python idiomatic.py 

real 0m4.492s 
user 0m0.000s 
sys  0m0.031s 

posso capisco perché la versione Python è più lenta - lo immagino ha molto a che fare con il chiamare xrange N volte, forse questo potrebbe essere eliminato se ci fosse un modo per riavvolgere un generatore. Tuttavia, con questa differenza di tempo di esecuzione, perché preferirebbe utilizzare la versione Pythonic?

Edit: Ho eseguito le prove di nuovo usando il codice signor Martelli fornito, ei risultati sono stati davvero meglio ora:

ho pensato di enumerare le conclusioni dal thread qui:

1) Un sacco di codice nell'ambito del modulo è una cattiva idea, anche se il codice è incluso in un blocco if __name__ == "__main__":.

2) * Curiosamente, modificando il codice che apparteneva a thebadone alla mia versione non corretta (lasciando y crescere senza reset) prodotta poca differenza in termini di prestazioni, anche per i valori più grandi di M e N.

+2

I tempi sono viziati, credo. Esegui più prove e probabilmente esegui alcuni calcoli al suo interno per eliminare ogni possibile ottimizzazione dal ciclo – Yuliy

+0

+1 Domanda molto interessante. Dopo aver letto la risposta di Martinelli, questa domanda è ancora più interessante per me, perché mostra le sottili differenze tra invocare un pezzo di codice dentro e fuori una funzione. – OscarRyz

+1

-1: poiché la base della domanda era un codice fondamentalmente incorretto, la prego di chiudere la domanda. –

risposta

22

Ecco il confronto corretto, ad es. in loop.py:

M = 10000 
N = 10000 

def thegoodone(): 
    for x in xrange(N): 
     for y in xrange(M): 
      pass 

def thebadone(): 
    x = 0 
    while x < N: 
     y = 0 
     while y < M: 
      y += 1 
     x += 1 

Tutto il codice sostanziale dovrebbe sempre essere nelle funzioni - mettendo un centinaio di milioni di cicli a livello superiore di un modulo mostra totale indifferenza per le prestazioni e si fa beffe di qualsiasi tentativo di misurare detta prestazione.

Una volta fatto questo, si veda:

$ python -mtimeit -s'import loop' 'loop.thegoodone()' 
10 loops, best of 3: 3.45 sec per loop 
$ python -mtimeit -s'import loop' 'loop.thebadone()' 
10 loops, best of 3: 10.6 sec per loop 

Quindi, adeguatamente misurati, il brutto modo che si Advocate è circa 3 volte più lento rispetto al buon modo che Python promuove. Spero che questo ti faccia riconsiderare la tua difesa errata.

+4

"Tutto il codice sostanziale dovrebbe essere sempre in funzione - mettere cento milioni di loop al livello più alto di un modulo mostra spericolata noncuranza per le prestazioni" (vedo). Puoi spiegare perché? –

+6

@Federico, ** velocità **. Il get e set variabile è altamente ottimizzato nelle funzioni, ma non può essere al livello più alto del modulo. Ad esempio, supponendo che il mio laptop e la macchina di Glenn siano equivalenti, dai nostri numeri si vede un fattore di 2 differenza nel fare le cose nel modo corretto (tutto il codice sostanziale nelle funzioni) rispetto al modo totalmente sbagliato (codice sostanziale al livello superiore del modulo). Benchmark te stesso! -) –

+1

Ho appena fatto. A livello di modulo: 14.3s vs 36.0s. Locale per funzionare: 8.6s contro 18.5s. (!!) Non lo sapevo, grazie. –

11

ti sei dimenticato di resettare y a 0 dopo il loop interno.

#!/usr/bin/env python 
M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    while x < N: 
     while y < M: 
      y += 1 
     x += 1 
     y = 0 

ndr: 20.63s dopo correzione vs. 6.97s utilizzando xrange

+0

Stavo per rispondere allo stesso modo. Il mio codice di ciclo while corretto correva circa 3 volte più lentamente del codice del ciclo. –

+1

In tutta serietà, no. Gli idiomi linguistici devono sempre tenere conto delle prestazioni ragionevoli; se l'idioma xrange in realtà * fosse * 40 volte più lento, sarebbe un idioma imperfetto che dovrebbe essere corretto o non più utilizzato.La leggibilità è importante, molto spesso anche a scapito di alcune prestazioni - ma non così tanto. –

+0

OK cancellato comunque la mia risposta ... –

3

buono per iterare su strutture dati

La sintassi for i in ... è grande per iterare su strutture di dati. In un linguaggio di livello inferiore, generalmente si sta eseguendo un iterazione su un array indicizzato da un int, ma con la sintassi python è possibile eliminare il passo di indicizzazione.

0

Ho ripetuto il test da @Alex Martelli's answer. Il idiomatica ciclo for è volte più veloce del ciclo while:

python -mtimeit -s'from while_vs_for import while_loop as loop' 'loop(10000)' 
10 loops, best of 3: 9.6 sec per loop 
python -mtimeit -s'from while_vs_for import for_loop as loop' 'loop(10000)' 
10 loops, best of 3: 1.83 sec per loop 

while_vs_for.py:

def while_loop(N): 
    x = 0 
    while x < N: 
     y = 0 
     while y < N: 
      pass 
      y += 1 
     x += 1 

def for_loop(N): 
    for x in xrange(N): 
     for y in xrange(N): 
      pass 

A livello di modulo:

$ time -p python for.py 
real 4.38 
user 4.37 
sys 0.01 
$ time -p python while.py 
real 14.28 
user 14.28 
sys 0.01 
1

questo non è un risposta diretta alla domanda, ma voglio aprire il dialogo un po 'di più su xrange(). due cose:

A. c'è qualcosa di sbagliato con una delle affermazioni OP che nessuno ha ancora corretto (sì, oltre al bug nel codice di reset non y):

"Io immagino ha molto a che fare con chiamando xrange N volte ...."

a differenza di conteggio tradizionale for loop, Python è più simile di foreach un guscio ... loop su un iterabile. pertanto, xrange() si chiama esattamente una volta, non "N volte".

B. xrange() B. xrange() è il nome di questa funzione in Python 2. sostituisce ed è rinominato in range() in Python 3, quindi tenetelo presente durante il porting. se non lo sapevi già, xrange() restituisce un iteratore (oggetto -like) mentre gli elenchi restituisce range(). dal momento che quest'ultimo è più inefficiente, è stato deprezzato a favore di xrange() che è più adatto alla memoria. la soluzione alternativa in Python 3, per tutti coloro che hanno bisogno di per avere una lista è list(range(N)).

+0

Ho pensato che ogni volta che il ciclo interno ha terminato tutte le iterazioni, un oggetto xrange (M) è stato nuovamente definito, perché AFAIK, i generatori non potevano essere riavvolti. – susmits

Problemi correlati