2012-05-12 11 views
7

Bawiłem się generatorami Pythona i klasą iteracyjną, tylko dla zabawy. Zasadniczo chciałem przetestować coś, czego nigdy nie byłem zbyt pewny: że klasy w Pythonach mają pewien znaczący narzut i lepiej jest polegać na metodach, które implementują yield zamiast klas, które implementują protokół iteratora, jeśli możesz.python - Overhead on looping nad klasą iterowalną

nie mogłem znaleźć satysfakcjonujące wyjaśnienie na ten temat w Google, więc postanowiłem przetestować je na własną rękę za pomocą tych dwóch prostych skryptów: func_iter.py i class_iter.py

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

A oto 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) 

I pobiegł każdy z nich 10 razy używając jes s w bash (dla class_iter.py, na przykład):

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

A oto średni czas eksploatacji dla każdej z nich:

class_iter.py: 0.0864 
func_iter.py: 0.0307 

Teraz moje pytania są następujące:

  1. Czy moje metody są poprawne? Czy moje porównanie jest sprawiedliwe?
  2. Jeśli tak, dlaczego ta duża różnica? Dlaczego class_iter.py wykonał prawie trzykrotnie dłużej niż func_iter.py?
  3. Jeśli nie, jak mogę poprawić swoje metody lub wymyślić lepsze porównanie?

EDIT: Jak sugeruje Dacav, próbowałem również uruchomiony func_iter.py użyciu xrange zamiast range. Zmniejsza to średni czas działania do 0.0263 sekund.

Odpowiedz

5

Wersja klasy spędza dużo czasu na uzyskiwaniu własnych zmiennych. Każdy cykl kosztów wynosi self.whatever.Jeśli zdefiniować __iter__ jako generator i zminimalizować wykorzystanie zmiennych instancji, różnica między wersjami klasy i funkcja będzie nieistotny:

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) 

Wyniki:

0.158941984177 
0.696810007095 
0.160784959793 

więc druga klasa generator jest prawie tak szybko, jak wersja funkcji.

Proszę pamiętać, że Generator i Generator2 na przykład nie są w pełni równoważne, istnieją przypadki, gdy nie można po prostu wymienić się „zwykły” iterator z generatorem (np Organizowanie).

+0

Nie sądzę, że właśnie tego chciał przetestować. Porównywujesz tutaj generator z generatorem, a nie generator do protokołu iteratora. Tak, klasa jest nadal możliwa do sprawdzenia, ale (na przykład) nie można jej wybrać, ponieważ stan jest generatorem, który nie jest członkiem klasy. – agf

+0

Potwierdzone! Wciąż trwa to dłużej niż 0,002 sekundy - czy można bezpiecznie założyć, że różnica ta wynika z czasu utworzenia klasy? – bow

+0

@bow: tak, tworzenie klasy + dostęp do zmiennej instancji w '__iter__'. Jeśli jesteś ciekawy, co dokładnie dzieje się za kulisami, wypróbuj moduł 'dis'. – georg

1

Jeśli korzystasz z Pythona, istnieją duże szanse, że nie będziesz dążyć do wydajności oprogramowania, ale zależy Ci na szybkim i zwinnym rozwoju.

Powiedział, że myślę, że metoda porównania jest całkiem sprawiedliwa, o ile twój kod jest wystarczająco inteligentny, aby uniknąć uprzedzeń dla jednego rozwiązania.

Na przykład, możliwym ulepszeniem dla wersji opartej na yield może być usunięcie reklamy funkcji range z użyciem funkcji xrange. Różnica (w python 2.x) polega na tym, że range buduje listę wartości (więc musi w tym celu przydzielić pamięć), podczas gdy xrange buduje obiekt iterowalny na podstawie podanych wartości.

+0

Dzięki! Właśnie próbowałem tego, a średni czas dla '' func_iter.py'' teraz zmniejsza się do 0.0263. – bow

1

Wydajesz się być całkowicie poprawny, a twoje porównanie jest sprawiedliwe. Gdy porównamy tylko narzut, klasa obsługująca protokół iteratora będzie wolniejsza niż funkcja generatora.

Jednak w realnym świecie, jeśli kod jest wystarczająco skomplikowane, aby uzasadnić klasę, czas działania algorytmu będzie karzeł napowietrznej, a więc będzie to zupełnie bez znaczenia dla wykonywania swojego programu.

Martwisz się tutaj o mikrooptymalizacje. Nie powinieneś. Skoncentruj się na pisaniu dobrego, czytelnego kodu i użyciu odpowiedniego algorytmu do pracy. Ilość czasu spędzonego na wyszukiwaniu atrybutów i wywołaniach metod w wersji klasy nie będzie wąskim gardłem.

+0

Ah :), moim zamiarem nie była tak naprawdę optymalizacja kodu produkcyjnego (choć może to trochę dotyczyć). Byłem po prostu ciekawy czegoś, o czym od dawna już myślałem (ale nigdy tak naprawdę nie udowodniłem) ~ i jestem pewien, że wiesz, że mity są bardzo zabawne: D. – bow

+0

@bow Próbuję powiedzieć, że zadajesz niewłaściwe pytanie. Nie ma znaczenia, jaka jest różnica prędkości, w granicach rozsądku. Liczy się wybór metody, dzięki której Twój kod będzie lepszy. Masz rację, że jeden jest wolniejszy, ale zły, że powinieneś myśleć o tym wszystkim. – agf

+0

@bow Warto również zauważyć, że jest to strona z prawdziwymi problemami, a nie teoretycznymi (zobacz FAQ), więc z pewnością uzyskasz przynajmniej kilka odpowiedzi, które odpowiedzą na pytanie, tak jakby nie było tylko akademickim. – agf

Powiązane problemy