2012-01-08 6 views
5

Pythonのアプリケーションをプロファイリングする際に、len()がセットを使用するときに非常に高価に見えることがわかりました。 lenA()lenB()よりも14倍遅くなるようですが、以下のプロファイラの統計によるとPython 3のlen(set)とset .__ len __()のパフォーマンスの比較

import cProfile 

def lenA(s): 
    for i in range(1000000): 
     len(s); 

def lenB(s): 
    for i in range(1000000): 
     s.__len__(); 

def main(): 
    s = set(); 
    lenA(s); 
    lenB(s); 

if __name__ == "__main__": 
    cProfile.run("main()","stats"); 

:以下のコードを参照してください

ncalls tottime percall cumtime percall filename:lineno(function) 
     1 1.986 1.986 3.830 3.830 .../lentest.py:5(lenA) 
1000000 1.845 0.000 1.845 0.000 {built-in method len} 
     1 0.273 0.273 0.273 0.273 .../lentest.py:9(lenB) 

私は何かが足りないのですか?現在、私が代わりにlen()__len__()を使用しますが、コードが汚れに見える:(明らかに

+7

なぜ、 'timeit'ではなく' cProfile'を使用していますか?前者は大規模なプログラムでボトルネックを発見するためのものであり、小規模である程度正確さを犠牲にしています。後者は、細かいスニペットの全体的なパフォーマンスを比較的正確に測定するためのものです。 'timeit'はこのようなマイクロベンチマークの最初の選択でなければなりません。そして、私にとってはそれほど極端な違いがないことを示しています( '' len'呼び出しあたり0.0879μs、 '' len''あたり0.158μs、 '' len''は70%遅い)。 – delnan

+0

ありがとう@delnan、私はPythonでかなり新しいです。 'timeit'を使っても同様の比率が得られます。実際、私のプログラムは上記のコードよりもはるかに大きいですが、 'len()'関数が大きなボトルネックの1つとして現れたことに私は驚きました。だから、私は 'len()'を無視し、自分の関数に焦点を当てるでしょう、そうですか? – Tregoreg

答えて

13

それは関数呼び出しを行います。TypeErrorからAttributeErrorを変換する。また、set.__len__はそれがバインドされ、このような簡単な操作であるため、lenは、いくつかのオーバーヘッドを持っていますtimeitを使用しているときだけについては何と比較して非常に高速であるが、私はまだ14倍の違いのようなものを見つけることができません:

In [1]: s = set() 

In [2]: %timeit s.__len__() 
1000000 loops, best of 3: 197 ns per loop 

In [3]: %timeit len(s) 
10000000 loops, best of 3: 130 ns per loop 

あなたはいつもちょうどlen、ない__len__を呼び出す必要がありlenへの呼び出しがある場合。ボトルネックあなたのプログラムでは、そのデザインを再考するべきです。キャッシュのサイズをどこかに設定するか、lenを呼び出さずに計算します。

+0

+1:特に、時期尚早に最適化しないでください。ベンチマークには欠陥があり、今見たように、3つのベンチマークは3つの異なる結果を返すでしょう。このようなマイクロベンチマークで期待していたものとまったく異なるものをベンチマークすることになるかもしれません。明らかに、 'len'は' __len__'を呼び出すので、より高速にすることはできません。しかしそれは確かなことすべてについてです。 –

+2

@ Anony-Mousse:実際には、私自身の結果をもう一度見て、私は今、 'len'が' __len__'よりも速いことだけを見ています。それがどうなったか分かりません。 –

+2

's .__ len__'も関数呼び出しを行いますが、*と*は属性をルックアップする必要があります。それは 'len'の世界的なルックアップを上回ります。 – WolframH

1

これはコメントになるだろうが、彼の論争の結果と私が得た結果に関するlarsmanのコメントの後、スレッドに自分のデータを追加するのは面白いと思う。

しようとすると、多かれ少なかれ、私はOPが得た反対だ、とlarsmanでコメントし、同じ方向に同じセットアップ:

12.1964105975 <- __len__ 
6.22144670823 <- len() 

C:\Python26\programas> 

テスト:

def lenA(s): 
    for i in range(100): 
     len(s); 

def lenB(s): 
    for i in range(100): 
     s.__len__(); 

s = set() 

if __name__ == "__main__": 

    from timeit import timeit 
    print timeit("lenB(s)", setup="from __main__ import lenB, s") 
    print timeit("lenA(s)", setup="from __main__ import lenA, s") 

これは2.6 ActivePythonのです。 7 64bit in win7

3

これはプロファイラーに関する興味深い見解で、len関数の実際のパフォーマンスとは関係ありません。あなたはプロファイラの統計では、lenAに関する二行があり、以下を参照してください。

ncalls tottime percall cumtime percall filename:lineno(function) 
     1 1.986 1.986 3.830 3.830 .../lentest.py:5(lenA) 
1000000 1.845 0.000 1.845 0.000 {built-in method len} 

... lenBに関する唯一のラインがある間:プロファイラはlenAからそれぞれ単一の呼び出しを計時している

 1 0.273 0.273 0.273 0.273 .../lentest.py:9(lenB) 

lenまでですが、全体としてはlenBです。コールをタイミングすると、常にオーバーヘッドが追加されます。 lenAの場合、このオーバーヘッドは100万倍になります。

+1

あなたの意見は絶対に正確だと思います。 'cProfile'のオーバーヘッドは' len'関数の性能に関するものではありません。 – Tregoreg