Um Python zu beschleunigen, fassen Sie den Umfang der Berechnung des Sammlungstyps (Liste / Tupel / Wörterbuch / Satz) für jeden Zweck zusammen.

Es ist der 6. Tag von Python Part 2 Adventskalender 2019. Gestern war Mr. bluepost59 Notation für extreme Inklusion. Eine umfassende Einführung in Einschlussnotationsmuster, die zur Beschleunigung nützlich sind.

Dieser Artikel soll auch beschleunigen. Um die Leistung einer grundlegenderen Verarbeitung zu organisieren, möchte ich den Umfang der Berechnung des Python-Sammlungstyps (Liste / Tupel / Wörterbuch / Satz) nach Verwendung zusammenfassen.

Im Folgenden finden Sie einen detaillierten Artikel, in dem der Berechnungsaufwand für jeden Sammlungstyp zusammengefasst ist.

Auf der anderen Seite gab es nicht viele Artikel, in denen verschiedene Berechnungen vom Typ Sammlung für jede Anwendung übersehen werden konnten. Daher möchte ich etwas schreiben, das aus einem anderen Blickwinkel als Referenz verwendet werden kann.

Außerdem werde ich über Python schreiben, eine CPython-Implementierung. Daher ist es für andere Implementierungen wie PyPy möglicherweise nicht hilfreich.

Ausdrucksnotation des Berechnungsbetrags

In diesem Artikel wird die Zeitberechnung in $ O $ -Notation ausgedrückt. Sofern nicht anders angegeben, werden wir den ** durchschnittlichen Berechnungsbetrag ** erörtern. In diesem Artikel bezieht sich $ n $ auf die Länge des Zielsammlungstyps und $ k $ auf die Länge des Sammlungstyps, der hinzugefügt oder gelöscht werden soll.

Symbol Berechnungsbetrag Überblick
O(1) Ständige Zeit Die Verarbeitungsgeschwindigkeit hängt nicht von der Größe ab
O(n) Lineare Zeit Die Verarbeitungsgeschwindigkeit ist proportional zur Größe erster Ordnung

Ref: Informationen zur Berechnungsreihenfolge

Überprüfungsumgebung

Verwendungsliste

Länge bekommen

Datenstruktur Berechnungsbetrag Notation
list/tuple O(1) len(seq)
dictionary O(1) len(dic)
set O(1) len(sets)

Sie können die Länge jeder Datenstruktur unabhängig von ihrer Größe mit derselben Verarbeitungsgeschwindigkeit abrufen. Da die Länge nicht berechnet wird, sondern die Länge selbst gespeichert wird, sind nur die Referenzkosten erforderlich und betragen $ O (1) $.

Wertreferenz

Datenstruktur Berechnungsbetrag Notation
list/tuple O(1) seq[i]
O(k) seq[i:j]
dictionary O(1) dic[key]
set O(1) sets[i]

Werte können für jede Datenstruktur unabhängig von ihrer Größe mit derselben Geschwindigkeit referenziert werden. Der Slice hängt jedoch von der Länge des referenzierten Bereichs ab.

Wiederholung

Datenstruktur Berechnungsbetrag Notation
list/tuple O(n) for item in seq
dictionary O(n) for key in dic.keys()
O(n) for item in dic.values()
O(n) for key, item in dic.items()
set O(n) for item in sets

Jede Datenstruktur kann unabhängig von ihrer Größe mit ungefähr derselben Verarbeitungsgeschwindigkeit iteriert werden.

Allein in Bezug auf den Rechenaufwand entspricht das Iterieren des Index und das Verweisen auf den Wert in der Schleife der in der Tabelle beschriebenen Verarbeitung, in der Realität besteht jedoch ein erheblicher Geschwindigkeitsunterschied, der durch die in der Tabelle angegebene Methode kursiv dargestellt wird. Ich denke es ist gut.

def iter_index(seq:List[int], index:List[int]):
    """Itulate Index und verweisen in einer Schleife darauf"""
    for idx in index:
        seq[idx]

seq = list(range(10000))
index = list(range(len(seq)))
%timeit iter_index(seq, index)
# >>> 281 µs ± 4.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

def iter_list(seq:List[int]):
    """Direkte Iteration des Sammlungstyps"""
    for item in seq:
        item
        
%timeit iter_list(seq)
# >>> 119 µs ± 2.08 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# 2 ~3 mal schneller

im Betreiber

Datenstruktur Berechnungsbetrag Notation
list/tuple O(n) item in seq
dictionary O(1) key in dic.keys()
O(n) item in dic.values()
O(1) (key, item) in dic.items()
set O(1) item in sets

Wie Sie intuitiv sehen können, ist list / tuple $ O (n) $. Außerdem ist dictionary.values () $ O (n) $. Ich denke, es lohnt sich daran zu denken, dass der Rest $ O (1) $ ist. Dies liegt daran, dass der Schlüssel für den Wörterbuchtyp und der Satztyp in der Hash-Tabelle implementiert sind. Weitere Informationen finden Sie im folgenden Artikel. ref: Der Grund, warum es explosiv wurde, indem es in Python von "in list" zu "in set" wechselte

Fall, in dem "im Satz" langsamer ist

Es ist schneller, den Set-Typ für den In-Operator zu verwenden, aber in Wirklichkeit wird der Listentyp häufiger verwendet. Ich denke, dass es viele Fälle gibt, in denen der Listentyp in den Set-Typ konvertiert und dann an den In-Operator übergeben wird. .. In Anbetracht der Konvertierungskosten ist Vorsicht geboten. (Im Folgenden werden wir nur die Liste untersuchen, aber das gleiche Argument wird mit Tupel anstelle von Liste vorgebracht.) Wenn ich tatsächlich "in Liste", "in Satz" und "Umwandlung von Liste in Satz & in Satz" gemessen habe, wurden die folgenden Ergebnisse erhalten.

# 「in list」
lists = list(range(100000))
%timeit 50000 in lists
# >>> 622 µs ± 47.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

# 「in set」
sets = set(lists)
%timeit 50000 in sets
# >>> 54.8 ns ± 4.39 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

#"Konvertierung von Liste zu Set& in set」
%timeit 50000 in set(lists)
# >>> 2.94 ms ± 61.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Bei der Messung der Dampfbehandlung mit verschiedenen Größen,

size in set /im Listenverhältnis in set(list) /im Listenverhältnis
10 3 4
100 19 3
1000 112 3
10000 1118 3
100000 11350 5

Mit anderen Worten

Daher kann in den folgenden Fällen die Verwendung von "in set" ** eher langsamer werden **.

--Liste zum Festlegen der erforderlichen Konvertierung --Und die Häufigkeit, mit der "im Satz" im konvertierten Satz verwendet wird, beträgt weniger als etwa das Fünffache

Wertzuweisung

Datenstruktur Berechnungsbetrag Notation
list O(1) seq[i] = item
O(k) seq[i:j] = lists
dictionary O(1) dic[key] = item

Die Zuweisung bezieht sich auf eine Operation, die den Wert und nicht die Länge ändert. Der Wert von set kann nicht geändert werden und muss gelöscht und hinzugefügt werden.

Mehrwert

Datenstruktur Berechnungsbetrag Notation Bemerkungen
list O(1) seq.append(item) Mehrwert auf der Rückseite
O(n) seq.insert(item, i) Mehrwert für i th
O(k) seq.extend(list) Kombinieren Sie Listen dahinter
dictionary O(1) dic[key] = item Element zum Wörterbuch hinzufügen
O(k) dic.update(dic2) Wörterbücher kombinieren
set O(1) sets.add(item) Element zum Festlegen hinzufügen

Der Umfang der Berechnung für die Liste hängt davon ab, wo Sie den Wert hinzufügen. Ich denke, der Algorithmus sollte den Wert am Ende so weit wie möglich enthalten. Außerdem sind alle oben genannten Ergänzungen destruktiv (es sind keine mehr vorhanden, bevor sie hinzugefügt werden), sodass Sie unerwartete negative Auswirkungen haben können, wenn Sie den Umfang der Variablen nicht berücksichtigen.

Sequentielles Hinzufügen oder Verbinden

Bei der Entscheidung, welcher Wert nach dem Filtern mit einem bedingten Ausdruck hinzugefügt werden soll, stehen zwei Optionen zur Verfügung.

Intuitiv scheint es weniger verschwenderisch zu sein, nacheinander hinzuzufügen, aber in Python ist for langsam und es ist besser, die Einschlussnotation so oft wie möglich zu verwenden, so dass es ein subtiles Problem ist. Die Ergebnisse des Versuchs sind unten gezeigt. Aus dem Ergebnis geht hervor, dass es keinen großen Geschwindigkeitsunterschied gibt. Ich möchte diejenige verwenden, die bequem ist. Persönlich bevorzuge ich die letztere Methode, weil die Einschlussnotation weniger Nebenwirkungen hat (weil sie weniger Wertzuweisung / Definition erfordert).

aufführen

Wenn die Iteration des hinzuzufügenden Werts lang ist, scheint es schneller zu sein, eine Liste einmal in der Einschlussnotation zu erstellen und sie dann zu erweitern. Hinweis 1) Wenn Sie eine destruktive Addition vornehmen, ändert sich die Größe und die Messung ist ungenau. Kopieren Sie die Liste und senden Sie sie an die Funktion. Hinweis.2) Das Anhängen ist bekanntermaßen langsam. Weisen Sie es daher einer Variablen zu und verwenden Sie es dann in einer Schleife (Referenz: Geschwindigkeit der Python-Listeneinschlussnotation fr / items / 43f90e07e4cebe63aeb6)))

def addition_extend(base:List[int], add:List[int]):
    add = [f for f in add if f]
    base.extend(add)
    return base

def addition_append(base:List[int], add:List[int]):
    base_a = base.append
    for ad in add:
        if ad:
            base_a(ad)
    return base

base = list(range(10000))
add = list(range(10))
%timeit addition_extend(copy(base), add)
# >>> 43.9 µs ± 6.94 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit addition_append(copy(base), add)
# >>> 39 µs ± 66.1 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

base = list(range(10))
add = list(range(10000))
%timeit addition_extend(copy(base), add)
# >>> 373 µs ± 45.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit addition_append(copy(base), add)
# >>> 540 µs ± 27.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Wörterbuch

Ich habe auch das Wörterbuch ausprobiert. Das Wörterbuch scheint für die sequentielle Zuweisung schneller zu sein. Da die Schwankung jedoch groß ist, scheint es keinen großen Unterschied zu geben.

def addition_update(base:Dict[str, int], add:Dict[str, int]):
    add = {f: g for f, g in add.items() if g}
    base.update(add)
    return base

def addition_assign(base:Dict[str, int], add:Dict[str, int]):
    for ad, val in add.items():
        if val:
            base[ad] = val
    return base

base = {str(f): f for f in range(10000)}
add = {str(f): f for f in range(10)}
%timeit addition_update(copy(base), add)
# >>> 365 µs ± 62.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit addition_assign(copy(base), add)
# >>> 312 µs ± 16.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)


base = {str(f): f for f in range(10)}
add = {str(f): f for f in range(10000)}
%timeit addition_update(copy(base), add)
# >>> 1.16 ms ± 35.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit addition_assign(copy(base), add)
# >>> 919 µs ± 45.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Wert löschen

Datenstruktur Berechnungsbetrag Notation Bemerkungen
list O(1) seq.pop() Löschen Sie den Wert dahinter
O(n) seq.delete(i) Löschen Sie den i-ten Wert
dictionary O(1) dic.pop(key) Löschen Sie durch Angabe des Schlüssels
set O(1) sets.discard(item) Löschen Sie durch Angabe eines Werts

Die Liste sollte beim Löschen von Werten so weit wie möglich auf die Rückseite beschränkt sein. Das Problem ist, was passiert, wenn mehrere Werte gelöscht werden müssen. Intuitiv scheint die Wiederherstellung schneller zu sein, wenn die Anzahl der zu löschenden Werte zunimmt. Wir werden diesbezüglich die folgenden zwei Punkte untersuchen. Ich werde das Ergebnis zuerst schreiben.

--pop vs Slice: ** Slice ** sieht besser aus --delete vs recreate: ** recreate ** sieht besser aus

pop vs slice Pop eine Liste der Größe n k mal für $ O (k) $ und Slice für $ O (n-k) $. Wenn jedoch n> k ist, kann es kein Pop sein, und wenn n <k, kann es kein Slice sein, also habe ich die Geschwindigkeit mit dem folgenden Code gemessen.

def pop(seq:List[int], num:int):
    for i in range(num):
        seq.pop()

def slices(seq:List[int], num:int):
    seq[:-num]

Unten finden Sie eine Tabelle, die das Ausführungsgeschwindigkeitsverhältnis von Pop und Slice durch Ändern der Länge der Liste und der Anzahl der Löschvorgänge misst. *** Dies ist der Fall, wenn die Scheibe schneller war als der diagonale Teil ***. Grundsätzlich scheint Slice besser zu sein.

pop /Schnittverhältnis Anzahl der Löschungen: 1 10 100 1000
list size: 10 1.2 3.0
100 1.0 1.6 10.9
1000 0.6 0.7 2.1 32.5
10000 0.6 0.5 0.5 1.7

löschen vs neu erstellen

Wenn Sie die Liste der Größe n k Mal löschen, ist sie $ O (kn) $, und wenn Sie sie neu erstellen, ist sie $ O (n) $. Natürlich scheint es schneller zu sein, es neu zu erstellen, aber ich habe die Geschwindigkeit mit dem folgenden Code gemessen.

def delete(lists:List[int], cond:Set[int]):
    for con in cond:
        try:
            lists.remove(con)
        except ValueError:
                pass
    return lists

def recreate(lists:List[int], cond:Set[int]):
    return [f for f in lists if f not in cond]

In der folgenden Tabelle wird das Ausführungsgeschwindigkeitsverhältnis von Löschen und Neuerstellen durch Ändern der Länge der Liste und der Anzahl der Löschvorgänge gemessen. *** Dies ist der Fall, wenn der Wiederaufbau für den schrägen Teil schneller war ***. Grundsätzlich scheint die Neuerstellung besser zu sein.

delete /Verhältnis neu erstellen Anzahl der Löschungen: 1 10 100 1000
list size: 10 0.7 1.5
100 0.3 1.3 3.5
1000 0.2 1.2 12.8 6.0
10000 0.3 1.8 114.7 117.4

Zusammenfassung

Der Umfang der Berechnung des Sammeltyps wird nach Verwendung zusammengefasst. Python soll eine langsame Sprache sein, aber ich hoffe, es hilft Ihnen, die Verarbeitungszeit zu bekommen, die Sie tolerieren können!

Morgen ist typecprint!

refs

Recommended Posts

Um Python zu beschleunigen, fassen Sie den Umfang der Berechnung des Sammlungstyps (Liste / Tupel / Wörterbuch / Satz) für jeden Zweck zusammen.
Liste der grundlegenden Operationen für Python3-Listen, -Tapples, -Wörterbücher und -Sätze
So schreiben Sie einen Listen- / Wörterbuchtyp von Python3
Ruft den Wert eines bestimmten Schlüssels bis zum angegebenen Index der Wörterbuchliste in Python ab
Python Hinweis: Map - Machen Sie dasselbe für jedes Element der Liste
[Python] Ich habe versucht, den kollektiven Typ (Satz) auf leicht verständliche Weise zusammenzufassen.
Python> sys.path> Liste der Zeichenfolgen, die den Pfad für die Suche nach Modulen angeben
Python Amateur versucht die Liste zusammenzufassen ①
Ich habe die Geschwindigkeit der Listeneinschlussnotation für und während mit Python2.7 gemessen.
Organisieren Sie Python-Tools, um die anfängliche Bewegung von Datenanalyse-Wettbewerben zu beschleunigen
Python-Skript zum Abrufen einer Liste von Eingabebeispielen für den AtCoder-Wettbewerb
So erhalten Sie Elemente vom Typ Wörterbuch von Python 2.7
Python Amateur versucht die Liste zusammenzufassen ②
Verwendung für Python-Stapel und -Warteschlangen (Geschwindigkeitsvergleich jeder Datenstruktur)
Ich habe die Geschwindigkeit der Referenz des Pythons in der Liste und die Referenz der Wörterbucheinbeziehung aus der In-Liste verglichen.
Geben Sie für jede Datei die angegebene Tabelle der Oracle-Datenbank in Python in Excel aus
So zählen Sie die Anzahl der Vorkommen jedes Elements in der Liste in Python mit der Gewichtung
So legen Sie die Entwicklungsumgebung für jedes Projekt mit VSCode + Python-Erweiterung + Miniconda fest
[Python] Ich habe versucht, das Array, die Wörterbuchgenerierungsmethode, die Schleifenmethode und die Listeneinschlussnotation zusammenzufassen
Zusammenfassung der Python-Sortierung (Liste, Wörterbuchtyp, Serie, DataFrame)
Ich habe versucht, die String-Operationen von Python zusammenzufassen
Lerndatensatz (6. Tag) #Set-Typ #Dictionary-Typ #Mutuelle Konvertierung des Listen-Taple-Sets #ndarray-Typ #Pandas (DataFrame-Typ)
Berücksichtigung von Python-Dekoratoren des Typs, der Variablen übergibt
Berechnen des aus ABC134-D gelernten Rechenaufwands
Ermitteln Sie die Anzahl der Vorkommen für jedes Element in der Liste
Ich möchte den Wörterbuchtyp in der Liste eindeutig machen
Ich habe versucht, den Inhalt jedes von Python pip gespeicherten Pakets in einer Zeile zusammenzufassen
Python-Liste, für Anweisung, Wörterbuch
Für Mac einrichten (Python)
So stellen Sie die Ausgabeauflösung für jeden Keyframe in Blender ein
So ändern Sie die Protokollstufe von Azure SDK für Python
Extrahieren Sie den Index der ursprünglichen Mengenliste, der der Liste der Teilmengen entspricht.
So überprüfen Sie die Speichergröße eines Wörterbuchs in Python
Wie nutzt man maschinelles Lernen für die Arbeit? 01_ Den Zweck des maschinellen Lernens verstehen
So richten Sie die Entwicklungsumgebung von ev3dev ein [Windows-Version]
[Python] Ein Programm, das den Inhalt der Liste nach links dreht
[Python] Erstellen einer Wörterbuchtypliste, Hinzufügen / Ändern / Löschen von Elementen und Extrahieren mit einer for-Anweisung
[AtCoder für Anfänger] Sprechen Sie über den Rechenaufwand, den Sie grob wissen möchten