Die Verwendung der insert
Funktion einer Liste ist viel langsamer als die Erzielung des gleichen Effekts mithilfe der Slice-Zuweisung:
> python -m timeit -n 100000 -s "a=[]" "a.insert(0,0)"
100000 loops, best of 5: 19.2 usec per loop
> python -m timeit -n 100000 -s "a=[]" "a[0:0]=[0]"
100000 loops, best of 5: 6.78 usec per loop
(Beachten Sie, dass dies a=[]
nur das Setup ist. a
Beginnt also leer, wächst dann aber auf 100.000 Elemente.)
Zuerst dachte ich, dass es vielleicht die Attributsuche oder der Funktionsaufruf-Overhead oder so ist, aber das Einfügen gegen Ende zeigt, dass das vernachlässigbar ist:
> python -m timeit -n 100000 -s "a=[]" "a.insert(-1,0)"
100000 loops, best of 5: 79.1 nsec per loop
Warum ist die vermutlich einfachere dedizierte Funktion "Einzelelement einfügen" so viel langsamer?
Ich kann es auch bei repl.it reproduzieren :
from timeit import repeat
for _ in range(3):
for stmt in 'a.insert(0,0)', 'a[0:0]=[0]', 'a.insert(-1,0)':
t = min(repeat(stmt, 'a=[]', number=10**5))
print('%.6f' % t, stmt)
print()
# Example output:
#
# 4.803514 a.insert(0,0)
# 1.807832 a[0:0]=[0]
# 0.012533 a.insert(-1,0)
#
# 4.967313 a.insert(0,0)
# 1.821665 a[0:0]=[0]
# 0.012738 a.insert(-1,0)
#
# 5.694100 a.insert(0,0)
# 1.899940 a[0:0]=[0]
# 0.012664 a.insert(-1,0)
Ich verwende Python 3.8.1 32-Bit unter Windows 10 64-Bit.
repl.it verwendet Python 3.8.1 64-Bit unter Linux 64-Bit.
python
performance
Heap-Überlauf
quelle
quelle
a=[]; a[0:0]=[0]
das gleiche tut wiea=[]; a[100:200]=[0]
a=[1,2,3];a[100:200]=[4]
wird4
am Ende der Listea
interessant angehängt .a=[]; a[0:0]=[0]
odera[0:0]=[0]
dasselbe wiea[100:200]=[0]
...Antworten:
Ich denke , es ist wahrscheinlich nur , dass sie vergessen haben , zu verwenden
memmove
inlist.insert
. Wenn Sie sich den Code ansehen, mit demlist.insert
Elemente verschoben werden, sehen Sie, dass es sich nur um eine manuelle Schleife handelt:Während
list.__setitem__
auf dem Slice-Zuweisungspfad verwendet wirdmemmove
:memmove
In der Regel werden viele Optimierungen vorgenommen, z. B. die Nutzung von SSE / AVX-Anweisungen.quelle
-O3
aktivierter automatischer Vektorisierung erstellt wurde, kann diese manuelle Schleife effizient kompiliert werden. Wenn der Compiler die Schleife jedoch nicht als memmove erkennt und zu einem tatsächlichen Aufruf von kompiliertmemmove
, kann er nur die zum Zeitpunkt der Kompilierung aktivierten Befehlssatzerweiterungen nutzen. (Gut, wenn Sie Ihre eigenen mit-march=native
erstellen, nicht so sehr für Distribution-Binärdateien, die mit Baseline erstellt wurden). Und GCC rollt Schleifen standardmäßig nicht ab, es sei denn, Sie verwenden PGO (-fprofile-generate
/ run /...-use
)memmove
Aufruf kompiliert , alle zur Ausführungszeit vorhandenen Erweiterungen nutzen kann?cpuid
). Gleiches gilt für mehrere andere mem / str-Funktionen. Distros können also nur kompiliert werden-O2
, um Binärdateien zu erstellen , aber memcpy / memmove verwenden zumindest eine nicht gerollte AVX-Schleife, die 32 Bytes pro Befehl lädt / speichert. (Oder sogar AVX512 auf den wenigen CPUs, auf denen das eine gute Idee ist; ich denke nur Xeon Phi.)memmove
Versionen in libc.so, der gemeinsam genutzten Bibliothek. Für jede Funktion erfolgt der Versand einmal während der Symbolauflösung (frühzeitige Bindung oder beim ersten Aufruf mit herkömmlicher verzögerter Bindung). Wie gesagt, es überlädt / hakt nur, wie dynamische Verknüpfungen stattfinden, nicht durch Umschließen der Funktion selbst. (speziell über den ifunc-Mechanismus von GCC: code.woboq.org/userspace/glibc/sysdeps/x86_64/multiarch/… ). Verwandte: Für Memset ist die übliche Wahl auf modernen CPUs__memset_avx2_unaligned_erms
diese Q & A