Leistungsunterschied verstehen

8

Antwort diese Frage Ich sah eine interessante Situation 2 ähnlicher Code Snippets haben sich ganz anders verhalten. Ich frage hier nur, um den Grund dafür zu verstehen und meine Intuition für solche Fälle zu verbessern.

Ich werde Code-Snippets für Python 2.7 anpassen (in Python 3 sind die Leistungsunterschiede gleich).

%Vor%

Ausgabe:

%Vor%

Die erste Lösung muss Suchen in OrderedDictionary durchführen, um value für jede key zu erhalten. Die zweite Lösung iteriert nur durch OrderedDictionary Schlüssel-Wert-Paare, die in Tupel gepackt werden müssen.

Die zweite Lösung ist 2 mal langsamer.

Warum ist das so?

Ich habe kürzlich dieses Video angeschaut, wo Raymond Hettinger sagt, dass Python tendiert Tupel wiederverwenden, also keine zusätzlichen Zuweisungen.

Also, was macht dieses Leistungsproblem aus?

Ich möchte ein wenig darauf eingehen, warum ich frage.

Die erste Lösung hat Dictionary-Lookup. Es impliziert, dass key hash genommen wird, dann nach Bin durch diesen Hash gesucht wird, dann Schlüssel aus diesem Bin abgerufen wird (hoffentlich wird es keine Schlüsselkollisionen geben) und dann Wert mit diesem Schlüssel assoziiert bekommen.

Die zweite Lösung durchläuft einfach alle Bins und liefert alle Schlüssel in diesen Bins. Es geht durch alle Behälter eins nach dem anderen ohne einen Rechenaufwand, den ich zu nehmen habe. Ja, es muss auf Werte zugreifen, die diesen Schlüsseln zugeordnet sind, aber der Wert ist nur einen Schritt vom Schlüssel entfernt, während die erste Lösung die Hash-Bin-Schlüssel-Wert-Kette durchlaufen muss, um den Wert zu erhalten, wenn er benötigt wird. Jede Lösung muss den Wert erhalten, der erste bekommt sie durch die Hash-Bin-Schlüssel-Wert-Kette, der zweite bekommt sie beim Zugriff auf den Schlüssel einen weiteren Zeiger folgen. Der einzige Overhead der zweiten Lösung ist, dass dieser Wert zusammen mit dem Schlüssel im temporären Tupel gespeichert werden muss. Es stellt sich heraus, dass diese Speicherung der Hauptgrund für den Overhead ist. Dennoch verstehe ich nicht ganz, warum es so ist, angesichts der Tatsache, dass es eine sogenannte "Tupel-Wiederverwendung" gibt (siehe das oben erwähnte Video).

Meiner Meinung nach muss die zweite Lösung zusammen mit dem Schlüssel Wert sparen, aber es vermeidet, dass wir Hash-Bin-Key-Berechnungen und Zugriffe durchführen müssen, um Wert für diesen Schlüssel zu erhalten.

    
ovgolovin 14.07.2013, 14:24
quelle

4 Antworten

6

Die Leistungsunterschiede werden hauptsächlich von OrderedDict verursacht.
OrderedDict verwendet dict 's get und __getitem__ , hat aber seine eigene __iter__ und iteritems .

%Vor%

Schauen Sie sich an, was wir gefunden haben: self[k] .
Ihre zweite Lösung hat uns nicht dabei geholfen, die Hash-Bin-Key-Berechnung zu vermeiden. Während der Iterator von dict generiert wird, genauer gesagt, items.iteritems().next() Wenn items ein dict ist, wird diese Berechnung nicht durchgeführt.

Außerdem ist iteritems auch teurer.

%Vor%

Ausgabe

%Vor%

Vergleichen mit iterkeys ' dictiter_iternextkey und itervalues ' dictiter_iternextvalue , iteritems ' dictiter_iternextitem enthält zusätzliche Teile.

%Vor%

Ich denke, dass Tupel-Erstellung die Leistung verringern könnte.

Tatsächlich neigt Python dazu, Tupel wiederzuverwenden.
tupleobject.c zeigt

%Vor%

Diese Optimierung bedeutet nur, dass Python einige Tupel nicht von Grund auf neu erstellt. Aber es gibt noch viel zu tun.

Fall: dict

Wenn OrderedDict durch dict ersetzt wird, denke ich, dass die zweite Lösung im Allgemeinen etwas besser ist.
Python-Wörterbücher werden mithilfe von Hashtabellen implementiert. Die Suche ist also schnell. Die durchschnittliche Zeitkomplexität der Suche ist O (1), während die schlechteste O (n) <1> ist. Die durchschnittliche Zeitkomplexität Ihrer ersten Lösung entspricht der zeitlichen Komplexität Ihrer zweiten Lösung. Sie sind beide O (n). Daher hat die zweite Lösung keine Vorteile oder ist sogar manchmal langsamer, insbesondere wenn die Eingabedaten klein sind. In diesem Fall konnten die durch iteritems verursachten zusätzlichen Kosten nicht kompensiert werden.

%Vor%

Ausgabe

%Vor%

Ersetzen Sie dann N und xs durch

%Vor%

Ausgabe

%Vor%

Ersetzen Sie nun N und xs durch

%Vor%

Ausgabe

%Vor%

Schließlich beginnt die zweite Lösung zu scheinen.

Der schlimmste Fall für die erste Lösung: Hash-Kollision

Lassen Sie

%Vor%

Ausgabe

%Vor%

1 Die für dict-Objekte aufgeführten Average-Case-Zeiten setzen voraus, dass die Hash-Funktion für die Objekte ausreichend robust ist, um Kollisionen zu vermeiden. Der Average Case geht davon aus, dass die in den Parametern verwendeten Schlüssel gleichmäßig aus der Menge aller Schlüssel ausgewählt werden. Siehe TimeComplexity .

    
nymk 24.07.2013, 22:01
quelle
3

Für Tupelwiederverwendung glaube ich es nicht:

%Vor%

Sie können von int oder string sehen:

%Vor%

bearbeiten:

Für dict sind Ihre beiden Methoden ähnlich. Lass es uns versuchen:

%Vor%

Obwohl sich die Zeit ändern kann, sind sie im Allgemeinen korrekt. Die Verwendung von iteritems und itemgetter ist so schnell wie get .

Aber für OrderedDict , versuchen wir es noch einmal:

%Vor%

Sie können sehen, dass für OrderedDict , verwenden Sie get und die eine iterator und itemgetter zeitlich variieren.

Also, ich denke der Zeitunterschied liegt an der Implementierung von OrderedDict . Tut mir leid, ich weiß nicht warum.

    
zhangyangyu 14.07.2013 14:52
quelle
2

Wie Sie selbst erwähnt haben, gibt es einen Unterschied zwischen den Funktionen.

Wo die erste Funktion über eine Liste von Strings iteriert, geht sie für jede Zeichenkette zum Wörterbuch und sucht nach dem Wert, dann findet sie das Minimum und kehrt zurück.

Die zweite Funktion iteriert über Tupel von string / int-Paaren. und dann greift es für jedes auf das zweite Element (den int / value) zu und findet dann das Minimum (in diesem Fall ein Tupel) und gibt dann das erste Element zurück.

Die zweite Funktion macht viel mehr Arbeit an Objekten, die viel mehr Verarbeitung benötigen (Tupel & gt; Strings) und dann (Tupel & gt; ints), plus dem zusätzlichen Gegenstandsabruf.

Warum sind Sie überrascht?

    
Inbar Rose 14.07.2013 14:35
quelle
1

Um auf meine vorherige Antwort zu erweitern. Um eine bessere Übersicht über die Vorgänge zu erhalten, können Sie immer das Modul dis verwenden.

%Vor%

Wie Sie sehen, ist in f2 noch viel mehr passiert (und deshalb wäre es logisch, dass es langsamer ist)

Sie können immer das Modul dis verwenden, um alles in Python zu überprüfen ein guter Hinweis darauf, was besser funktioniert.

Man kann immer das Modul timeit verwenden, um das Timing oder die Leistung bestimmter Methoden zu überprüfen Funktionen, die sie für bestimmte Eingabearten ausführen, aber manchmal kann das Timing deaktiviert sein, da der verwendete Beispieldatensatz für eine bestimmte Funktion besser geeignet ist als für eine andere, z. B. beim Sortieren einer Sortierfunktion würde bereits eine bestimmte Art von Funktion gegenüber einer anderen bevorzugen, die schneller eine weniger sortierte Liste sortieren kann, aber keine von diesen berücksichtigt verschiedene Arten von Daten, die sich möglicherweise in den Listen selbst befinden. Die Verwendung des dis -Moduls vermeidet die meisten davon, indem man direkt sehen kann, was Python hinter dem Vorhang (oder unter der Haube), der einen viel klareren Hinweis darauf gibt, welche Methode am besten geeignet ist, bestimmte Aufgaben auszuführen.

    
Inbar Rose 23.07.2013 09:37
quelle

Tags und Links