2012-01-20 31 views
5

Özel bir yineleyici ile küçük bir kapsayıcı üzerinde yinelenen oldukça şaşırtıcı performans farklılıkları gibi görünüyor karşılaşıyorum. Birinin bu farklılıkların nereden geldiğini anlamama yardımcı olabileceğini umuyordum.özel yineleyici performansı

İlk önce bazı içerik; Ben getitem uygulayan bir 3d float vektör türüne bir bağlayıcı içeren boost :: python kullanarak bir dizi python uzantısı modülleri yazıyorum. Bunun üzerinden geçmesi mümkün olsa da, oldukça yavaş görünüyor, ancak neden bu kadar açık değil, bu yüzden işlerin nasıl yürüdüğüne dair daha iyi bir fikir edinmek için pythondaki bazı basit özel yineleyicilerle oynamaya karar verdim. Bu yineleyiciler nereden geldiğini olduğunu ...

class MyIterator1(object): 
    __slots__ = ['values', 'popfn'] 

    def __init__(self): 
     self.values = ['x', 'y', 'z'] 
     self.popfn = self.values.pop 

    def __length_hint__(self): 
     return 3 

    def __iter__(self): 
     return self 

    def next(self): 
     try: 
      return self.popfn() 
     except IndexError: 
      raise StopIteration 

class MyIterator2(object): 
    __slots__ = ['values', 'itfn'] 

    def __init__(self): 
     self.values = ['x', 'y', 'z'] 
     it = iter(self.values) 
     self.itfn = it.next 

    def __length_hint__(self): 
     return 3 

    def __iter__(self): 
     return self 

    def next(self): 
     return self.itfn() 

class MyIterator3(object): 
    __slots__ = ['values', 'i'] 

    def __init__(self): 
     self.values = ['x', 'y', 'z'] 
     self.i = 0 

    def __length_hint__(self): 
     return 3 

    def __iter__(self): 
     return self 

    def next(self): 
     if self.i >= 3: 
      raise StopIteration 
     value = self.values[self.i] 
     self.i += 1 
     return value 

def MyIterator4(): 
    val = ['x', 'y', 'z'] 
    yield val[0] 
    yield val[1] 
    yield val[2] 

Sonraki Ben

import timeit 

timer1 = timeit.Timer('r = list(testiter.MyIterator1())', 'import testiter') 
timer2 = timeit.Timer('r = list(testiter.MyIterator2())', 'import testiter') 
timer3 = timeit.Timer('r = list(testiter.MyIterator3())', 'import testiter') 
timer4 = timeit.Timer('r = list(testiter.MyIterator4())', 'import testiter') 
timer5 = timeit.Timer('r = list(iter(["x", "y", "z"]))', 'import testiter') 

print 'list(testiter.MyIterator1())' 
print timer1.timeit() 

print "\n" 

print 'list(testiter.MyIterator2())' 
print timer2.timeit() 

print "\n" 

print 'list(testiter.MyIterator3())' 
print timer3.timeit() 

print "\n" 

print 'list(testiter.MyIterator4())' 
print timer4.timeit() 

print "\n" 

print 'list(iter(["x", "y", "z"]))' 
print timer5.timeit() 

Bu baskılar (yukarıdaki kod bir modül olarak adlandırılan testiter olduğunu varsayar) sürümüyle gelen timeit modülü ile bir komut dosyası aracılığıyla bunlar ran aşağıdaki

list(testiter.MyIterator1()) 
8.5735929


list(testiter.MyIterator2()) 
5.28959393501 


list(testiter.MyIterator3()) 
6.11230111122 


list(testiter.MyIterator4()) 
2.31263613701 


list(iter(["x", "y", "z"])) 
1.26243281364 

üzerinden Beklenildiği piton listiterator oldukça farkla, en hızlı. Bunun python içerisinde bazı sihir optimizasyonlarına bağlı olduğunu farz ediyorum. Jeneratör de MyIterator sınıflarından çok daha hızlıdır, ki bu da yine de pek şaşırmam, ve c'deki tüm işlere bağlı olduğunu varsayalım, ancak bu sadece bir tahmindir. Şimdi diğerleri daha kafa karıştırıcı/şaşırtıcı. Deneme/istisna ifadeleri bu bağlamda göründüğü kadar pahalı mı yoksa başka bir şey mi oluyor?

Bu farklılıkların açıklanmasında yardımcı olacak her şeyden mutluluk duyacağız! Uzun mesaj için özür dilerim.

+2

Sen yerine tuple kullanmayı deneyebilirsiniz [ 'x', 'y', 'z'] (mümkünse): tuple inşaat alittle biraz daha hızlı liste en fazla. –

cevap

2

Başımın tepesinden birkaç fikir; Özür yeterince aşikar değilseniz:

  • ilk yineleyici her öğe alınır sonra mutasyona uğrar listeyi anlamına liste en popnext uygulamaya kullanıyor. Belki de dinamik olarak tahsis edilmiş birleşik veri yapısının bu mutasyonu, performansı düşürmektedir. Hepsi listelerin uygulama ayrıntılarına bağlıdır, bu nedenle bu tamamen alakasız olabilir.
  • Son yineleyici, sonucu üretmek için basit bir bağlamda dile (verim) bağlı bir özellik kullanıyor. Bir tahminde, bunun, aynı sonucu elde etmeye çalışan özel bir yineleyici sınıfından daha fazla optimizasyon alanı olduğunu belirteceğim.
  • 5.uncu zamanlayıcı, özel bir yineleyici kullanmıyor ve bunun yerine doğrudan doğruya listelenmiş olanı kullanıyor. Listeler için yineleyiciler muhtemelen en iyi duruma getirilmişlerdir ve bu özel sınıfların bir kısmı zaten içte böyle bir liste yineleyici kullandığını kullanan bir dolaylılık katmanı yoktur.
+1

Bu yükseltmeyi ve bir istisnayı yakalamanın nispeten pahalı olduğunu ekleyebilirim: http://paltman.com/2008/01/18/try-except-performance-in-python-a-simple-test/. – Noah

+0

Ah, elbette. Komik, ne kadar özlediğim şeyleri özlüyorum =) Python ve Ruby'nin istisnaları neden böyle şeyler için kullanıldığını asla anlamamıştım ... – Louis