2012-05-12 9 views
7

Stavo armeggiando con i generatori Python e la classe iterabile, solo per divertimento. Fondamentalmente volevo testare qualcosa di cui non sono mai stato tanto sicuro: le classi in Pythons hanno un overhead significativo ed è meglio fare affidamento sui metodi che implementano yield invece di classi che implementano un protocollo iteratore, se è possibile.python - Overhead su looping su una classe iterable

non riuscivo a trovare una spiegazione soddisfacente su questo argomento in Google, così ho deciso di provare loro fuori da solo con questi due semplici script: func_iter.py e class_iter.py

Ecco func_iter.py:

#!/usr/bin/env python 

import time 

x = 0 
def create_generator(num): 
    mylist = range(num) 
    for i in mylist: 
     yield i 

t = time.time() 
gen = create_generator(100000) 

for i in gen: 
    x = x + i 

print "%.3f" % (time.time() - t) 

Ed ecco class_iter.py:

#!/usr/bin/env python 

import time 

x = 0 

class Generator(object): 

    def __init__(self, num): 
     self.start = 0 
     self.end = num 

    def __iter__(self): 
     return self 

    def next(self): 
     if self.start == self.end: 
      raise StopIteration 
     else: 
      self.start = self.start + 1 
      return self.start 

t = time.time() 
gen = Generator(100000) 

for i in gen: 
    x = x + i 

print "%.3f" % (time.time() - t) 

allora ho fatto funzionare ciascuno di essi 10 volte utilizzando Thi s in bash (per class_iter.py, per esempio):

for i in {1..10}; do ./class_iter.py; done 

E qui sono i tempi medi di funzionamento per ciascuna di esse:

class_iter.py: 0.0864 
func_iter.py: 0.0307 

Ora, le mie domande sono:

  1. Are i miei metodi sono corretti? Il mio confronto è giusto?
  2. Se sì, perché la grande differenza? Perché lo class_iter.py ha impiegato quasi tre volte il tempo di esecuzione di func_iter.py?
  3. In caso contrario, come posso migliorare i miei metodi o ottenere un confronto migliore?

EDIT: Come suggerito Dacav, ho anche provato a fare funzionare func_iter.py utilizzando xrange invece di range. Ciò riduce il tempo di esecuzione medio a 0,0263 secondi.

risposta

5

La versione di classe impiega molto tempo ad accedere alle proprie variabili. Ogni ciclo di costi self.whatever.Se si definisce la vostra __iter__ come generatore e ridurre al minimo l'uso di variabili di istanza, la differenza tra le versioni di classe e la funzione sarà trascurabile:

setup = """ 
def create_generator(num): 
    mylist = range(num) 
    for i in mylist: 
     yield i 

class Generator(object): 

    def __init__(self, num): 
     self.start = 0 
     self.end = num 

    def __iter__(self): 
     return self 

    def next(self): 
     if self.start == self.end: 
      raise StopIteration 
     else: 
      self.start = self.start + 1 
      return self.start 

class Generator2(object): 

    def __init__(self, num): 
     self.mylist = range(num) 

    def __iter__(self): 
     for i in self.mylist: 
      yield i 
""" 

import timeit 

print timeit.timeit('for p in create_generator(1000):p', setup, number=1000) 
print timeit.timeit('for p in Generator(1000):p', setup, number=1000) 
print timeit.timeit('for p in Generator2(1000):p', setup, number=1000) 

Risultati:

0.158941984177 
0.696810007095 
0.160784959793 

così la seconda classe del generatore è quasi veloce come la versione della funzione.

Si prega di notare che Generator e Generator2 nell'esempio non sono del tutto equivalente, ci sono casi in cui non si può semplicemente sostituire un iteratore "semplice" con un generatore (ad esempio marshalling).

+0

Non penso che sia quello che voleva testare. Stai confrontando un generatore con un generatore qui, non con un generatore per il protocollo iteratore. Sì, la classe è ancora iterabile, ma (ad esempio) non è possibile mettere sotto controllo lo stato perché lo stato è un generatore che non è un membro della classe. – agf

+0

Confermato! È ancora più lento per forse 0,002 secondi ~ è sicuro assumere che questa differenza è dovuta al tempo necessario per istanziare la classe? – bow

+0

@bow: sì, istanza di classe + accesso alla variabile di istanza in '__iter__'. Se sei curioso di vedere cosa succede esattamente dietro le quinte, prova il modulo 'dis'. – georg

1

Se si utilizza Python ci sono buone probabilità che non si miri alle prestazioni del software, ma si preoccupa di più di essere rapido e agile nello sviluppo.

Detto questo, penso che il metodo di confronto sia abbastanza equo purché il codice sia abbastanza intelligente da evitare pregiudizi per una soluzione.

Ad esempio, un possibile miglioramento per la versione basata su yield potrebbe rimuovere la funzione range utilizzando la funzione xrange. La differenza (in python 2.x) è che range crea un elenco di valori (quindi deve allocare spazio nella memoria per esso) mentre xrange crea un oggetto iterabile che varia sui valori specificati.

+0

Grazie! Ho appena provato questo, e il tempo medio per '' func_iter.py'' ora diminuisce a 0.0263. – bow

1

Sembra che tu sia completamente corretto e il tuo paragone è giusto. Quando si confronta solo l'overhead, la classe che supporta il protocollo iteratore sarà più lenta di una funzione di generatore.

Tuttavia, nel mondo reale, se il codice è complicato abbastanza per giustificare una classe, il tempo di esecuzione dell'algoritmo farà impallidire l'overhead, e quindi sarà del tutto irrilevante per il tempo di esecuzione del programma.

Qui ti preoccupi delle micro-ottimizzazioni. Non dovresti. Concentrati sulla scrittura di un codice buono e leggibile e sull'algoritmo corretto per il lavoro. La quantità di tempo speso per le ricerche di attributi e le chiamate di metodo nella versione di classe non sarà il collo di bottiglia.

+0

Ah :), la mia intenzione non era proprio l'ottimizzazione di un codice di produzione (anche se questo potrebbe riguardare un po '). Ero solo curioso di qualcosa che ho pensato a lungo prima (ma mai veramente provato) ~ e sono sicuro che sai sfatare i miti è divertente: D. – bow

+0

@bow Sto cercando di dire che stai facendo la domanda sbagliata. Non importa quale sia la differenza di velocità, entro limiti ragionevoli. Ciò che conta è scegliere il metodo che rende il tuo codice migliore. Hai ragione che uno è più lento, ma è sbagliato che tu stia pensando a questo a tutti. – agf

+0

@bow Vale anche la pena notare che questo è un sito per problemi reali, non teorici (vedi le FAQ), quindi è necessario avere almeno alcune risposte che rispondano alla domanda come se non fosse solo accademica. – agf