2015-08-27 12 views
46

Die Komplexität von len() in Bezug auf Sätze und Listen ist gleichermaßen O (1). Wie kommt es, dass es mehr Zeit braucht, um Sets zu bearbeiten?Komplexität von len() in Bezug auf Sätze und Listen

~$ 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 

Ist es auf die jeweilige Benchmark bezogen, wie in, ist es Zeit zu bauen Sätze als Listen und die Benchmark berücksichtigt auch das braucht?

Wenn die Erstellung eines Mengenobjekts im Vergleich zum Erstellen einer Liste mehr Zeit in Anspruch nimmt, was wäre der Grund dafür?

+9

Ihr letzter Satz ist wahrscheinlich richtig - beim Hinzufügen von Elementen zu einem Set ist Hashing beteiligt. –

+3

Sie können versuchen, den Block ohne 'len()' zu überprüfen :) – Caramiriel

+0

@Caramiriel oder zu zwei Strings und pass-'s Option :) – Maroun

Antwort

107

Erstens Sie nicht die Geschwindigkeit von len() gemessen haben, haben Sie die Geschwindigkeit der Erstellung einer Liste gemessen/set zusammen mit die Geschwindigkeit der len().

Verwenden Sie das --setup Argument von timeit:

$ 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 

Die Anweisungen, die Sie zu --setup passieren, werden ausgeführt, bevor die Geschwindigkeit des len() messen.

Zweitens, sollten Sie beachten, dass len(a) ist eine ziemlich schnelle Aussage. Der Vorgang der Geschwindigkeitsmessung kann "Rauschen" unterliegen. Bedenken Sie, dass the code executed (and measured) by timeit dem folgenden entspricht:

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

Da sowohl len(a) und itertools.repeat(...).__next__() sind schnelle Operationen und ihre Geschwindigkeiten können ähnlich sein, die Geschwindigkeit der itertools.repeat(...).__next__() die Zeiten beeinflussen können.

Aus diesem Grunde sollten Sie besser Maß len(a); len(a); ...; len(a) (100-mal wiederholt oder so), so dass der Körper der for-Schleife eine wesentlich höhere Menge an Zeit in Anspruch nimmt als der Iterator:

$ 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 

(Die Ergebnisse noch sagt, dass len() die gleichen Leistungen auf Listen und Sätzen, aber jetzt sind Sie sicher, dass das Ergebnis korrekt ist.)

Drittens es stimmt, dass „Komplexität“ und „Geschwindigkeit“ beziehen, aber ich glaube, Sie machen etwas Verwirrung. Die Tatsache, dass die Komplexität von Listen und Mengen len()O (1) bedeutet, bedeutet nicht, dass sie auf Listen und Mengen mit derselben Geschwindigkeit laufen muss.

Es bedeutet, dass im Durchschnitt, egal wie lange die Liste a ist, len(a) führt die gleiche asymptotische Anzahl von Schritten. Und egal wie lange der Satz b ist, len(b) führt die gleiche asymptotische Anzahl von Schritten aus. Aber der Algorithmus für die Berechnung der Größe von Listen und Mengen kann unterschiedlich sein, was zu unterschiedlichen Leistungen führt (Zeit zeigt, dass dies nicht der Fall ist, dies kann jedoch eine Möglichkeit sein).

Schließlich

Wenn die Erzeugung eines Satzes Objekt mehr Zeit zum Erstellen einer Liste verglichen nimmt, was wäre der eigentliche Grund sein?

Ein Satz, wie Sie wissen, erlaubt keine wiederholten Elemente. Sets in CPython sind als Hashtabellen implementiert (um sicherzustellen, dass der Durchschnittswert O (1) Insertion und Lookup): Das Erstellen und Pflegen einer Hashtabelle ist wesentlich komplexer als das Hinzufügen von Elementen zu einer Liste.

Insbesondere beim Erstellen einer Menge müssen Sie Hashes berechnen, die Hash-Tabelle erstellen, sie nachschlagen, um das Einfügen duplizierter Ereignisse zu vermeiden, und so weiter. Im Gegensatz dazu sind Listen in CPython als ein einfaches Array von Zeigern implementiert, die wie erforderlich malloc() ed und realloc() ed sind.

+2

Wow, große Dissektion und Erklärung der Gefahren von Leistungsmessungen. Vielen Dank. –

5

Ja, Sie haben Recht, es ist mehr wegen der unterschiedlichen Zeit erforderlich für die Erstellung der set und list Objekte von Python. Als fairer Benchmark können Sie timeit Modul verwenden und die Objekte mit setup Argument übergeben:

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") 

Ergebnis:

1st: 0.04927110672 
2nd : 0.0530669689178 

Und wenn Sie wissen wollen, warum es wie so ist, der Python lässt durch Welt. Tatsächlich gesetzt Objekt verwenden hash table und eine Hash-Tabelle verwendet eine Hash-Funktion zum Erstellen der Hash-Werte der Elemente und Mapping sie auf die Werte und in diesem Deal den Aufruf der Funktion und die Berechnung der Hash-Werte und einige andere zusätzliche Aufgaben werden viel Zeit dauern. Während zum Erstellen einer Liste Python nur eine Sequenz von Objekten erstellen, auf die Sie mit der Indexierung zugreifen können.

Sie können weitere Details zu set_lookkey Funktion von Cpython source code überprüfen.

Beachten Sie auch, dass wenn zwei Algorithmen die gleiche Komplexität haben, es nicht bedeutet, dass beide Algorithmen genau die gleiche Laufzeit oder Ausführungsgeschwindigkeit haben.


weil big O Notation beschreibt die limiting behavior of a function und zeigt nicht die genaue Komplexität Gleichung. Zum Beispiel ist die Komplexität der folgenden Gleichungen f(x)=100000x+1 und f(x)=4x+20 O (1) und es bedeutet, dass beide lineare Gleichungen bur sind, wie Sie sehen können, hat die erste Funktion eine sehr viel größere Steigung, und für eine Eingabe geben sie unterschiedliche Ergebnisse .

1

Entfernen Sie die len(a) Aussage. Das Ergebnis ist ziemlich gleich. Ein Set muss hashed sein, um nur bestimmte Elemente zu behalten, damit es langsamer ist.

18

Die entsprechenden Linien sind http://svn.python.org/view/python/trunk/Objects/setobject.c?view=markup#l640

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

und 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  } 

Beide sind nur eine statische Lookup.

Also was ist der Unterschied, den Sie fragen können. Sie messen auch die Erstellung der Objekte. Und es ist ein wenig zeitaufwändiger, ein Set als eine Liste zu erstellen.

6

Verwenden Sie diese mit der -s Flagge timeit ohne unter Berücksichtigung der ersten Saite:

~$ 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 
          ↑ 

jetzt nur nur die len Funktion bedenkt es, und die Ergebnisse sind ziemlich das gleiche, da wir die Erstellungszeit des Sets/der Liste nicht berücksichtigt haben.

3

Lassen Sie mich die ausgezeichneten Antworten hier zusammenfügen: O(1) erzählt nur über die order of growth in Bezug auf die Größe des Eingangs.

O(1) bedeutet insbesondere nur konstante Zeitin Bezug auf die Größe des Eingangs. Verfahren kann immer 0,1s nehmen, für jeden Eingang und eine weitere 1000 Jahre für jede Eingabe in Anspruch nehmen, und sie würden beide sein O(1)

In diesem Fall, während die Dokumentation eine gewisse Zweideutigkeit hat, es bedeutet, dass die Methode ungefähr die gleiche Zeit dauert, um eine Liste der Größe 1 zu bearbeiten, wie es dauert, um Liste der Größe 1000 zu verarbeiten; In ähnlicher Weise dauert es auch, ein Wörterbuch der Größe 1 so zu bearbeiten, wie es erforderlich ist, um ein Wörterbuch der Größe 1000 zu verarbeiten.

Für die verschiedenen Datentypen wird keine Garantie gegeben.

Dies ist nicht überraschend, da die Implementierung von len() an irgendeiner Stelle in der Aufrufliste je nach Datentyp abweichen kann.

Übrigens diese Mehrdeutigkeit in statisch typisierten Sprachen eliminiert wo ClassA.size() und ClassB.size() für alle Absichten und purpouses zwei verschiedene Methoden.

1

Viele haben darauf hingewiesen, dass O (1) ist nicht um Leistung auf die verschiedenen Datentypen , aber über die Leistung als Funktion der verschiedenen Eingangsgrößen .

Wenn Sie versuchen, O (1) -ness zu testen, würden Sie für etwas mehr wie

~$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 

Big Daten oder nur wenige Daten, genommen suchen, die Zeit ist ganz ähnlich. Bei anderen Posts unterscheidet dies die Setup-Zeit von der Testzeit, geht jedoch nicht so weit, dass das Rauschen der Len-Zeit gegenüber der Loop-Zeit verringert wird.

Verwandte Themen