7

特定のフィルターに一致するリスト内のアイテムの数を数える最も簡単な方法を見つけようとしていました。この場合、リストに奇数がいくつあるかを見つけます。

これを行っている間、リスト内包表記と同等のジェネレータ式を比較した結果に驚きました。

python -m timeit -s "L = xrange(1000000)" "sum([1 for i in L if i & 1])"
10 loops, best of 3: 109 msec per loop

python -m timeit -s "L = xrange(1000000)" "sum(1 for i in L if i & 1)"
10 loops, best of 3: 125 msec per loop

また、Lを通常のリストとし、サイズを変えてみましたが、すべての場合でリスト内包表記が優先されます。

100万アイテムの新しいリストを作成するlistcompと比較して、genexpの動作が遅くなる原因は何ですか...?

(ところで、私が見つけた最速の方法は次のとおりx = 1; len(filter(x.__and__, L))です。そして、はい、私は子猫を殺すようなコードを書くことを知っています、私はそれを楽しむためにそれをやっています)

4

2 に答える 2

15

本質的に無制限のメモリが利用可能な場合(実際の問​​題ではない場合が多いですが、小さなベンチマークでは常に当てはまります!-)、リストは1つの「大きな束」で一度だけ割り当てられるため、ジェネレータよりもパフォーマンスが優れている傾向があります(メモリの断片化などはありませんが、ジェネレータは、スタックフレームの状態を保持して実行を再開できるようにすることで、その「大きな束」のアプローチを回避するために(内部的に)余分な労力を必要とします。

リストアプローチとジェネレータアプローチのどちらが実際のプログラムで高速になるかは、「マイクロベンチマーク」で正確に再現することはほぼ不可能である断片化を含む正確なメモリ状況に依存します。IOWは、最終的に、パフォーマンスを本当に気にする場合は、一般的な場合、「おもちゃ」のマイクロベンチマークだけでなく、実際のプログラムを慎重にベンチマーク(および個別にプロファイル)する必要があります。

于 2010-01-31T23:29:29.997 に答える
3

私が覚えていることから、リスト内包表記は1つのアクティブ化フレームを使用するのに対し、ジェネレーターフレームは結果ごとにアクティブ化する必要があります。リスト圧縮の増分コストは、メモリの追加コストです。この場合、intへの参照です。各アイテムが新しいインスタンスであり、より多くのメモリを使用する場合、関係は逆になる可能性があります。

更新:テスト後、逆になりました

~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum([oint(1) for i in L if i & 1])" 
10 loops, best of 3: 414 msec per loop

~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum(oint(1) for i in L if i & 1)" 
10 loops, best of 3: 392 msec per loop
于 2010-01-31T23:31:59.827 に答える