Ich dachte, es könnte sich lohnen, einen Mikro-Benchmark durchzuführen, in dem die Laufzeiten der verschiedenen hier genannten Ansätze verglichen werden.
Haftungsausschluss: Ich benutze simple_benchmark
(eine von mir geschriebene Bibliothek) für die Benchmarks verwenden und auch iteration_utilities.count_items
(eine von mir geschriebene Funktion in einer Drittanbieter-Bibliothek).
Um ein differenzierteres Ergebnis zu erhalten, habe ich zwei Benchmarks durchgeführt, einen nur mit den Ansätzen, die keinen Zwischencontainer bauen, um ihn dann wegzuwerfen, und einen mit diesen:
from simple_benchmark import BenchmarkBuilder
import more_itertools as mi
import iteration_utilities as iu
b1 = BenchmarkBuilder()
b2 = BenchmarkBuilder()
@b1.add_function()
@b2.add_function()
def summation(it):
return sum(1 for _ in it)
@b1.add_function()
def len_list(it):
return len(list(it))
@b1.add_function()
def len_listcomp(it):
return len([_ for _ in it])
@b1.add_function()
@b2.add_function()
def more_itertools_ilen(it):
return mi.ilen(it)
@b1.add_function()
@b2.add_function()
def iteration_utilities_count_items(it):
return iu.count_items(it)
@b1.add_arguments('length')
@b2.add_arguments('length')
def argument_provider():
for exp in range(2, 18):
size = 2**exp
yield size, [0]*size
r1 = b1.run()
r2 = b2.run()
import matplotlib.pyplot as plt
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=[15, 18])
r1.plot(ax=ax2)
r2.plot(ax=ax1)
plt.savefig('result.png')
Die Ergebnisse waren:
Es werden log-log-Achsen verwendet, so dass alle Bereiche (kleine Werte, große Werte) untersucht werden können. Da die Diagramme für einen qualitativen Vergleich gedacht sind, sind die tatsächlichen Werte nicht allzu interessant. Im Allgemeinen steht die y-Achse (vertikal) für die Zeit und die x-Achse (horizontal) für die Anzahl der Elemente in der Eingabe-"iterable". Je niedriger der Wert auf der vertikalen Achse, desto schneller.
Die obere Grafik zeigt die Ansätze, bei denen keine Zwischenliste verwendet wurde. Dies zeigt, dass die iteration_utilities
war der schnellste Ansatz, gefolgt von more_itertools
und am langsamsten war die Verwendung von sum(1 for _ in iterator)
.
Das untere Diagramm enthält auch die Ansätze, bei denen len()
auf einer Zwischenliste, einmal mit list
und einmal mit einem Listenverständnis. Der Ansatz mit len(list)
war hier am schnellsten, aber der Unterschied zum iteration_utilities
Ansatz ist nahezu vernachlässigbar. Der Ansatz mit dem Verständnis war deutlich langsamer als der mit list
direkt.
Zusammenfassung
Jeder hier erwähnte Ansatz zeigte eine Abhängigkeit von der Länge der Eingabe und iterierte über jedes Element in der Iterable. Es gibt keine Möglichkeit, die Länge ohne die Iteration zu ermitteln (selbst wenn die Iteration ausgeblendet ist).
Wenn Sie keine Erweiterungen von Drittanbietern wünschen, sollten Sie len(list(iterable))
ist definitiv der schnellste Ansatz der getesteten Ansätze, er erzeugt jedoch eine Zwischenliste, die könnte erheblich mehr Speicherplatz verwenden.
Wenn Sie nichts gegen zusätzliche Pakete haben, dann iteration_utilities.count_items
wäre fast so schnell wie die len(list(...))
Funktion, benötigt aber keinen zusätzlichen Speicherplatz.
Es ist jedoch wichtig zu beachten, dass der Mikro-Benchmark eine Liste als Eingabe verwendet. Das Ergebnis des Benchmarks könnte anders ausfallen, je nachdem, welche Iterabilien Sie abfragen möchten. Ich habe auch getestet mit range
und eine einfache Genertor-Expression und die Trends waren sehr ähnlich, aber ich kann nicht ausschließen, dass sich das Timing nicht je nach Art der Eingabe ändert.
0 Stimmen
Verwandt: Länge eines endlichen Generators