2012-01-20 5 views
5

カスタムイテレータを使用して小さなコンテナを反復処理するときに、かなり驚くべきパフォーマンスの違いに遭遇しています。私は、誰かが、これらの違いがどこから来ているのか理解するのを助けることができるかもしれないことを望んでいました。カスタムイテレータのパフォーマンス

最初にいくつかのコンテキストがあります。私はboost :: pythonを使っていくつかのPython拡張モジュールを書いています。そのうちの1つは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] 

次は私が

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

この印刷物(上記のコードは、モジュールと呼ばれるtestiter内にあるとみなされます)は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 

驚くべきことに、python listiteratorは、かなりのマージンで最も速いです。私は、これがpython内のいくつかの魔法の最適化に至ったと考えています。ジェネレータはMyIteratorクラスよりもかなり高速ですが、これもまた私は驚くことではありません。また、すべての作業がCで行われていることを前提としていますが、これは単なる推測です。今や他の人たちはもっと混乱し/賛成しています。 try/except文はこの文脈のように高価であるか、それとも何か他のことが起こっていますか?

これらの相違点を説明する助けがあれば、大歓迎です!長い投稿のための謝罪。

+2

['x'、 'y'、 'z']の代わりにタプルを使用できます(可能な場合)。タプルの作成はリストのビットよりも少し速いです。 –

答えて

2

私の頭の上のいくつかのアイデア。彼らは十分に触知いないのであれば申し訳ありません:

  • 最初のイテレータは、各要素が取得された後、リストが変異している意味、nextを実装するために、リストのpopを使用しています。ダイナミックに割り当てられた複合データ構造のこのような突然変異は、おそらくパフォーマンスを低下させます。すべてはリストの実装の詳細に依存しているので、これはまったく無関係かもしれません。
  • 最後のイテレータは、単純なコンテキストで言語に収束したフィーチャを使用して結果を生成しています。推測すると、同じ結果を達成しようとするカスタムイテレータクラスよりも、最適化の余地があることを示しています。
  • 5番目のタイマーはカスタムイテレータを使用しておらず、代わりにリストに提供されているものを直接使用しています。リストのイテレータはおそらく最適化されており、それらのカスタムクラスが使用する間接的なレイヤは存在しません。そのうちのいくつかは内部的にこのようなリストイテレータを使用しています。
+1

私はそれを追加し、例外をキャッチすることは比較的高価です:http://paltman.com/2008/01/18/try-except-performance-in-python-a-simple-test/。 – Noah

+0

ああ、もちろん。面白い、本当に明白なことを私が逃していること=)PythonとRubyが例外をそのようなものに使うべきだと思うのは、決して本当に理解できませんでした。 – Louis

関連する問題