5

私の質問への答えとして、2つのリストが同じである1ベースの位置を見つけて、Cライブラリitertoolsを使用して処理を高速化するためのヒントを得ました。

確認するために、cProfileを使用して次のテストをコーディングしました。

from itertools import takewhile, izip

def match_iter(self, other):
    return sum(1 for x in takewhile(lambda x: x[0] == x[1],
                                        izip(self, other)))

def match_loop(self, other):
    element = -1
    for element in range(min(len(self), len(other))):
        if self[element] != other[element]:
            element -= 1
            break
    return element +1

def test():
    a = [0, 1, 2, 3, 4]
    b = [0, 1, 2, 3, 4, 0]

    print("match_loop a=%s, b=%s, result=%s" % (a, b, match_loop(a, b)))
    print("match_iter a=%s, b=%s, result=%s" % (a, b, match_iter(a, b)))

    i = 10000
    while i > 0:
        i -= 1
        match_loop(a, b)
        match_iter(a, b)

def profile_test():
    import cProfile
    cProfile.run('test()')

if __name__ == '__main__':
    profile_test()

関数match_iter()はitertoolsを使用しており、関数match_loop()はプレーンPythonを使用する前に実装したものです。

関数test()は、2つのリストを定義し、2つの関数の結果をリストに出力して、機能していることを確認します。両方の結果の期待値は5で、これはリストが等しい場合の長さです。次に、両方の関数で10,000回ループします。

最後に、profile_test()を使用して全体のプロファイルを作成します。

私が学んだことは、izipはpython3のitertoolsに実装されておらず、少なくとも私が使用しているdebianwheezywhitでは実装されていないということでした。だから私はpython2.7でテストを実行しました

結果は次のとおりです。

python2.7 match_test.py
match_loop a=[0, 1, 2, 3, 4], b=[0, 1, 2, 3, 4, 0], result=5
match_iter a=[0, 1, 2, 3, 4], b=[0, 1, 2, 3, 4, 0], result=5
         180021 function calls in 0.636 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.636    0.636 <string>:1(<module>)
        1    0.039    0.039    0.636    0.636 match_test.py:15(test)
    10001    0.048    0.000    0.434    0.000 match_test.py:3(match_iter)
    60006    0.188    0.000    0.275    0.000 match_test.py:4(<genexpr>)
    50005    0.087    0.000    0.087    0.000 match_test.py:4(<lambda>)
    10001    0.099    0.000    0.162    0.000 match_test.py:7(match_loop)
    20002    0.028    0.000    0.028    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    10001    0.018    0.000    0.018    0.000 {min}
    10001    0.018    0.000    0.018    0.000 {range}
    10001    0.111    0.000    0.387    0.000 {sum}

不思議に思うのは、cumtimeの値を見ると、私のプレーンなpythonバージョンの値は10,000ループで0.162秒で、match_iterバージョンの値は0.434秒です。

一つには、Pythonは非常に高速で素晴らしいので、心配する必要はありません。しかし、これは正しいでしょうか?Cライブラリがジョブを完了するのに単純なPythonコードの2倍以上の時間がかかるということですか?それとも私は致命的な間違いをしていますか?

確認するために、python2.6でもテストを実行しました。これはさらに高速のようですが、ループとitertoolsの違いは同じです。

誰が経験豊富で喜んで手伝ってくれますか?

4

2 に答える 2

7
  • まず、実際に何かのタイミングを計ったことに対する称賛。
  • 第二に、読みやすさは通常、高速なコードを書くよりも重要です。コードの実行速度が3倍速いが、デバッグに3週間ごとに2を費やしている場合は、時間をかける価値はありません。
  • timeit第三に、コードの小さなビットの時間を計るために使用することもできます。そのアプローチは、を使用するよりも少し簡単だと思いますprofile。(profileただし、ボトルネックを見つけるのに適しています)。

itertools一般的に、かなり高速です。ただし、特にこの場合、takewhileitertoolsは途中ですべての要素に対して関数を呼び出す必要があるため、処理速度が低下します。Pythonの各関数呼び出しには、かなりのオーバーヘッドが関連付けられているため、少し遅くなる可能性があります(最初にラムダ関数を作成するコストもあります)。sumジェネレータ式を使用すると、オーバーヘッドも少し増えることに注意してください。ただし、最終的には、この状況では常に基本的なループが優先されるように見えます。

from itertools import takewhile, izip

def match_iter(self, other):
    return sum(1 for x in takewhile(lambda x: x[0] == x[1],
                                        izip(self, other)))

def match_loop(self, other):
    cmp = lambda x1,x2: x1 == x2

    for element in range(min(len(self), len(other))):
        if self[element] == other[element]:
            element += 1
        else:
            break

    return element

def match_loop_lambda(self, other):
    cmp = lambda x1,x2: x1 == x2

    for element in range(min(len(self), len(other))):
        if cmp(self[element],other[element]):
            element += 1
        else:
            break

    return element

def match_iter_nosum(self,other):
    element = 0
    for _ in takewhile(lambda x: x[0] == x[1],
                       izip(self, other)):
        element += 1
    return element

def match_iter_izip(self,other):
    element = 0
    for x1,x2 in izip(self,other):
        if x1 == x2:
            element += 1
        else:
            break
    return element



a = [0, 1, 2, 3, 4]
b = [0, 1, 2, 3, 4, 0]

import timeit

print timeit.timeit('match_iter(a,b)','from __main__ import a,b,match_iter')
print timeit.timeit('match_loop(a,b)','from __main__ import a,b,match_loop')
print timeit.timeit('match_loop_lambda(a,b)','from __main__ import a,b,match_loop_lambda')
print timeit.timeit('match_iter_nosum(a,b)','from __main__ import a,b,match_iter_nosum')
print timeit.timeit('match_iter_izip(a,b)','from __main__ import a,b,match_iter_izip')

ただし、最速のバージョンはloop+itertoolsのハイブリッドであることに注意してください。この(明示的な)ループオーバーizipも(私の意見では)読みやすくなっています。したがって、これから、必ずしも一般的takewhileではない、遅い部分であると結論付けることができます。itertools

于 2013-03-18T13:03:48.673 に答える
4

ここでの問題は、テストリストが小さいことだと思います。つまり、違いは最小限である可能性が高く、イテレータを作成するコストは、イテレータがもたらす利益を上回っています。

大規模なテスト(パフォーマンスが重要にsum()なる可能性が高い)では、使用しているバージョンが他のバージョンよりもパフォーマンスが優れている可能性があります。

また、スタイルの問題もあります。手動バージョンは長く、インデックスによる反復に依存しているため、柔軟性も低くなります。

私は、最も読みやすい解決策は次のようなものになると主張します。

def while_equal(seq, other):
    for this, that in zip(seq, other):
        if this != that:
            return
        yield this

def match(seq, other):
    return sum(1 for _ in while_equal(seq, other))

興味深いことに、私のシステムでは、これを少し変更したバージョンです。

def while_equal(seq, other):
    for this, that in zip(seq, other):
        if this != that:
            return
        yield 1

def match(seq, other):
    return sum(while_equal(seq, other))

純粋なループバージョンよりもパフォーマンスが優れています。

a = [0, 1, 2, 3, 4]
b = [0, 1, 2, 3, 4, 0]

import timeit

print(timeit.timeit('match_loop(a,b)', 'from __main__ import a, b, match_loop'))
print(timeit.timeit('match(a,b)', 'from __main__ import match, a, b'))

与える:

1.3171300539979711
1.291257290984504

そうは言っても、純粋なループバージョンをよりPythonicに改善すると、次のようになります。

def match_loop(seq, other):
    count = 0
    for this, that in zip(seq, other):
        if this != that:
            return count
        count += 1
    return count

今回は(上記と同じ方法を使用して)0.8548871780512854私にとっては、他のどの方法よりも大幅に高速でありながら、読み取り可能です。これはおそらく、元のバージョンのインデックスによるループが原因であり、通常は非常に低速です。ただし、最も読みやすいと思うので、この投稿の最初のバージョンを選択します。

于 2013-03-18T13:00:21.193 に答える