7

私は楽しみのために、Python のジェネレーターと反復可能なクラスをいじっていました。基本的に、あまり確信が持てないことをテストしたかったのです。つまり、Python のクラスにはかなりのオーバーヘッドがありyield、可能であれば、反復子プロトコルを実装するクラスではなく、実装するメソッドに依存する方がよいということです。

func_iter.pyGoogle でこのトピックに関する満足のいく説明を見つけることができなかったので、次の 2 つの簡単なスクリプトを使用して自分でテストすることにしました。class_iter.py

ここにあり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)

そして、ここにあります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)

次に、これを bash で使用して、それぞれを 10 回実行しました (class_iter.pyたとえば、 の場合)。

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

そして、それぞれの平均実行時間は次のとおりです。

class_iter.py: 0.0864
func_iter.py: 0.0307

さて、私の質問は次のとおりです。

  1. 私の方法は正しいですか?私の比較は公平ですか?
  2. もしそうなら、なぜ大きな違いがあるのですか?class_iter.py実行に約 3 倍の時間がかかったのはなぜfunc_iter.pyですか?
  3. そうでない場合、どのように方法を改善したり、より良い比較を考え出すことができますか?

編集: Dacav が示唆したように、代わりに をfunc_iter.py使用して実行してみました。これにより、平均実行時間が 0.0263 秒に短縮されます。xrangerange

4

3 に答える 3

6

クラス バージョンは、独自の変数へのアクセスに多くの時間を費やします。それぞれself.whateverにサイクルがかかります。をジェネレーターとして定義__iter__し、インスタンス変数の使用を最小限に抑えると、クラスと関数のバージョンの違いは無視できます。

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)

結果:

0.158941984177
0.696810007095
0.160784959793

したがって、2 番目のジェネレーター クラスは関数バージョンとほぼ同じくらい高速です。

Generatorこの例では、とは完全に同等ではないことに注意してください。Generator2単純に「プレーンな」イテレータをジェネレータ (マーシャリングなど) に置き換えることができない場合があります。

于 2012-05-12T17:07:15.050 に答える
1

あなたは完全に正しいようで、あなたの比較は公平です。オーバーヘッドのみを比較すると、イテレータ プロトコルをサポートするクラスは、ジェネレータ関数よりも遅くなります。

ただし、現実の世界では、クラスを正当化するのに十分なほどコードが複雑な場合、アルゴリズムの実行時間はオーバーヘッドを小さくするため、プログラムの実行時間にはまったく関係ありません。

ここでは、マイクロ最適化について心配しています。すべきではありません。適切で読みやすいコードを作成し、その仕事に適したアルゴリズムを使用することに集中してください。クラス バージョンでの属性検索とメソッド呼び出しに費やされる時間は、ボトルネックにはなりません。

于 2012-05-12T17:11:14.877 に答える
1

Python を使用している場合は、ソフトウェアのパフォーマンスを目標としていない可能性が高くなりますが、開発の迅速さと機敏性に関心があります。

そうは言っても、コードが1つのソリューションのバイアスを回避するのに十分スマートである限り、比較方法は非常に公平だと思います。

たとえば、yieldベースのバージョンで考えられる改善は、関数を削除して、代わりに関数rangeを使用することです。xrange(python 2.x での) 違いはrange、値のリストを作成する (そのため、メモリにスペースを割り当てる必要がある) 一方xrangeで、指定された値の範囲の反復可能なオブジェクトを作成することです。

于 2012-05-12T17:02:25.967 に答える