web-dev-qa-db-ja.com

セットとリストに関するlen()の複雑さ

セットとリストに関するlen()の複雑さも同様にO(1)です。セットの処理に時間がかかるのはなぜですか?

~$ python -m timeit "a=[1,2,3,4,5,6,7,8,9,10];len(a)"
10000000 loops, best of 3: 0.168 usec per loop
~$ python -m timeit "a={1,2,3,4,5,6,7,8,9,10};len(a)"
1000000 loops, best of 3: 0.375 usec per loop

特定のベンチマークに関連していますか?リストよりもセットの作成に時間がかかり、ベンチマークもそれを考慮に入れていますか?

リストオブジェクトの作成と比較して、セットオブジェクトの作成に時間がかかる場合、根本的な理由は何でしょうか?

51
Omid

最初に、len()の速度を測定していません。リスト/セットの作成速度を測定しました一緒にlen()の速度。

timeitの_--setup_引数を使用します。

_$ python -m timeit --setup "a=[1,2,3,4,5,6,7,8,9,10]" "len(a)"
10000000 loops, best of 3: 0.0369 usec per loop
$ python -m timeit --setup "a={1,2,3,4,5,6,7,8,9,10}" "len(a)"
10000000 loops, best of 3: 0.0372 usec per loop
_

_--setup_に渡すステートメントは、len()の速度を測定する前に実行されます。

2番目に、len(a)はかなり迅速なステートメントであることに注意してください。その速度を測定するプロセスは、「ノイズ」の影響を受ける場合があります。 timeitによって実行(および測定)されたコード は次と同等であると考えてください。

_for i in itertools.repeat(None, number):
    len(a)
_

len(a)itertools.repeat(...).__next__()はどちらも高速な演算であり、速度も同じであるため、itertools.repeat(...).__next__()の速度がタイミングに影響する場合があります。

このため、len(a); len(a); ...; len(a)(100回程度繰り返す)を測定して、forループの本体にイテレーターよりもかなり長い時間がかかるようにすることをお勧めします。

_$ python -m timeit --setup "a=[1,2,3,4,5,6,7,8,9,10]" "$(for i in {0..1000}; do echo "len(a)"; done)"
10000 loops, best of 3: 29.2 usec per loop
$ python -m timeit --setup "a={1,2,3,4,5,6,7,8,9,10}" "$(for i in {0..1000}; do echo "len(a)"; done)"
10000 loops, best of 3: 29.3 usec per loop
_

(結果はlen()がリストとセットに対して同じパフォーマンスを持っていることを示していますが、結果は正しいことが確実です。)

第3に、「複雑さ」と「速度」が関連しているのは事実ですが、混乱していると思います。 len()がリストとセットに対してO(1)複雑であるという事実は、リストとセットで同じ速度で実行する必要があることを意味しません。

これは、平均して、リストaがどのくらい長くても、len(a)が同じ漸近数のステップを実行することを意味します。そして、セットbがどのくらい長くても、len(b)は同じ漸近数のステップを実行します。しかし、リストとセットのサイズを計算するためのアルゴリズムは異なる場合があり、異なるパフォーマンスをもたらします(これはそうではないことを示していますが、これは可能性があるかもしれません)。

最後に、

リストオブジェクトの作成と比較して、セットオブジェクトの作成に時間がかかる場合、根本的な理由は何ですか?

ご存知のように、セットでは要素を繰り返すことはできません。 CPythonのセットはハッシュテーブルとして実装されます(平均O(1)挿入とルックアップを確実にするため):ハッシュテーブルの作成と維持は、リストに要素を追加するよりもはるかに複雑です。

具体的には、セットを作成するときは、ハッシュを計算し、ハッシュテーブルを作成し、重複するイベントの挿入を回避するためにそれを検索する必要があります。対照的に、CPythonのリストは、必要に応じてmalloc() edおよびrealloc() edであるポインターの単純な配列として実装されます。

112

関連する行は http://svn.python.org/view/python/trunk/Objects/setobject.c?view=markup#l64

640     static Py_ssize_t
641     set_len(PyObject *so)
642     {
643         return ((PySetObject *)so)->used;
644     }

および http://svn.python.org/view/python/trunk/Objects/listobject.c?view=markup#l431

431     static Py_ssize_t
432     list_length(PyListObject *a)
433     {
434         return Py_SIZE(a);
435     }

どちらも静的検索のみです。

だからあなたが尋ねるかもしれない違いは何ですか。オブジェクトの作成も測定します。また、リストを作成するよりも、セットを作成するほうが少し時間がかかります。

20
kay

これを-sフラグと一緒に使用して、最初の文字列を考慮してなしを計時します。

~$ python -mtimeit -s "a=range(1000);" "len(a)"
10000000 loops, best of 3: 0.0424 usec per loop
                           ↑ 

~$ python -mtimeit -s "a={i for i in range(1000)};" "len(a)"
10000000 loops, best of 3: 0.0423 usec per loop
                           ↑ 

今はlen関数のみを考慮しているだけであり、セット/リストの作成時間を考慮していなかったため、結果はほとんど同じです。

6
Maroun

はい、そうです。Pythonでsetオブジェクトとlistオブジェクトを作成するのに必要な時間が異なるためです。より公平なベンチマークとして、timeitモジュールを使用し、setup引数を使用してオブジェクトを渡すことができます。

_from timeit import timeit

print '1st: ' ,timeit(stmt="len(a)", number=1000000,setup="a=set([1,2,3]*1000)")
print '2nd : ',timeit(stmt="len(a)", number=1000000,setup="a=[1,2,3]*1000")
_

結果:

_1st:  0.04927110672
2nd :  0.0530669689178
_

なぜそうなのかを知りたい場合は、python world。実際に設定されたオブジェクトが ハッシュテーブル とハッシュテーブルは、ハッシュ関数を使用してアイテムのハッシュ値を作成し、それらを値にマッピングします。この取引では、関数を呼び出し、ハッシュ値といくつかの追加のタスクを計算しますリストを作成する間、pythonは、インデックスを使用してアクセスできるオブジェクトのシーケンスを作成するだけです。

_set_lookkey_関数の詳細は Cpythonソースコード で確認できます。

また、2つのアルゴリズムの複雑度が同じである場合でも、両方のアルゴリズムの実行時間または実行速度がまったく同じであるとは限りません。1


_big O_表記は 関数の制限動作 を表し、正確な複雑さの式を示していないためです。たとえば、次の方程式f(x)=100000x+1f(x)=4x+20の複雑さはO(1)であり、これは、最初の関数の勾配はかなり大きく、同じ入力に対して異なる結果が得られます。

5
Kasrâmvd

ここで優れた答えをまとめてみましょう。O(1)は、入力のサイズに関して growth の順序についてのみ通知します。

特にO(1)は、一定時間入力のサイズに関してのみを意味します。メソッドはすべての入力に常に0.1秒かかり、別のメソッドはすべての入力に1000年かかる可能性があり、どちらもO(1)になります。

この場合、ドキュメントにはある程度のあいまいさがありますが、これは、メソッドが大まかにかかることを意味しますサイズのリストを処理するのと同じ時間1サイズ1000のリストを処理するには時間がかかります。同様に、サイズ1のディクショナリーの処理には、サイズ1000のディクショナリーの処理と同じ時間がかかります。

異なるデータ型に関して保証はありません

コールスタックのある時点でのlen()の実装は、データ型によって異なる場合があるため、これは当然のことです。

ちなみに静的に型付けされた言語では、このあいまいさが排除されますここで、ClassA.size()ClassB.size()はすべてのインテント用であり、2つの異なるメソッドを使用します。

3
Tobia Tesan

len(a)ステートメントを削除します。結果はほとんど同じです。セットは、別個のアイテムのみを保持するためにハッシュする必要があるため、遅くなります。

1
Code Different

多くの人が、O(1)はさまざまなデータ型のパフォーマンスについてではなく、さまざまな入力サイズの関数としてのパフォーマンスについて) 。

O(1)-nessをテストしようとしている場合は、もっと似たものを探しているでしょう。

~$python -m timeit --setup "a=list(range(1000000))" "len(a)"
10000000 loops, best of 3: 0.198 usec per loop

~$python -m timeit --setup "a=list(range(1))" "len(a)"
10000000 loops, best of 3: 0.156 usec per loop

ビッグデータでもリトルデータでも、かかる時間はほぼ同じです。他の投稿によると、これはセットアップ時間とテスト時間を分けていますが、len-timeとloop-timeのノイズを減らすことはできません。

1
Bryant