2012-01-20 159 views
5

我在使用自定義迭代器遍歷小容器時遇到了令人驚訝的性能差異。我希望有人能夠幫助我理解這些差異來自哪裏。自定義迭代器性能

首先是一些背景;我正在寫一些使用boost :: python的python擴展模塊,其中一個模塊包含一個綁定到實現getitem的3d float向量類型。因爲getitem有可能迭代它,但它看起來很慢,但它並不明顯,所以我決定在Python中使用一些簡單的自定義迭代器來更好地瞭解事情的工作原理。這也正是這些迭代器是從哪裏來的?

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] 

接下來,我通過與timeit模塊的腳本(假定上面的代碼是在一個模塊調用testiter)

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

此打印這些跑出以下

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 

不出所料蟒蛇的ListIterator是最快的,用相當的餘量。我認爲這是python中的一些魔術優化。生成器也比MyIterator類快得多,我再也沒有感到驚訝,並假設是由於所有工作都是在c中完成的,但這只是一個猜測。現在其他人更容易混淆/懇求。在這種情況下,嘗試/除外聲明看起來像是昂貴的還是別的什麼?

任何幫助解釋這些差異將不勝感激!對長期職位道歉。

+2

您可以嘗試使用元組而不是['x','y','z'](如果可能):元組構造比列表快一點。 –

回答

2

一些想法,我的頭頂;對不起,如果他們沒有足夠捫:

  • 第一個迭代器使用該列表的pop實施next,這意味着該列表中的每個元素檢索後發生突變。也許這種動態分配的複合數據結構的突變會降低性能。全部取決於列表的實現細節,所以這可能完全不相關。
  • 最後一個迭代器在簡單的上下文中使用連接到語言(yield)的特性來生成結果。有人猜測,我會說這表明與自定義迭代器類嘗試實現相同結果相比,有更多優化空間。
  • 第5個計時器未使用自定義迭代器,而是直接使用爲列表提供的一個。列表的迭代器可能是非常優化的,這些自定義類沒有使用間接層,其中一些內部無論如何都使用這樣的列表迭代器。
+1

我會補充說,提高和捕捉異常是相對昂貴的:http://paltman.com/2008/01/18/try-except-performance-in-python-a-simple-test/。 – Noah

+0

啊,當然。有趣的是,我錯過了真正顯而易見的事情=)我從來沒有真正理解爲什麼Python和Ruby認爲異常應該用於這樣的事情...... – Louis