2010-01-31 11 views
7

Ich habe versucht, die Anzahl der Elemente in einer Liste, die einem bestimmten Filter entsprechen, am schnellsten zu ermitteln. In diesem Fall finden Sie, wie viele ungerade Zahlen in einer Liste enthalten sind.Warum funktioniert dieses Genexp schlechter als ein Listenverständnis?

Während dies zu tun, wurde ich von den Ergebnissen überrascht eine Liste Verständnis vs dem äquivalenten Generator Ausdruck zu vergleichen:

python -m timeit -s "L = xrange(1000000)" "sum([1 for i in L if i & 1])" 
10 loops, best of 3: 109 msec per loop 

python -m timeit -s "L = xrange(1000000)" "sum(1 for i in L if i & 1)" 
10 loops, best of 3: 125 msec per loop 

ich auch versucht habe, mit L eine reguläre Liste, und verschiedenen Größen zu sein, aber in allen Fälle gewinnt das Listenverständnis.

Was macht das Genexp, das es langsamer im Vergleich zu dem Listcomp verursacht, das eine neue Liste mit 1 Million Elementen erstellt ...?

(Btw, der schnellste Weg, ich fand, war: x = 1; len(filter(x.__and__, L)) Und ja, ich weiß, das Schreiben von Code wie das tötet Kätzchen, ich es für den Spaß von ihm mache.)

Antwort

15

Wenn im Wesentlichen unbegrenzter Speicher verfügbar ist (was in kleinen Benchmarks immer der Fall sein wird, obwohl oft nicht in realen Problemen! -), werden Listen Generatoren übertreffen, weil sie nur einmal in einem "großen Haufen" (keine Speicherfragmentierung, etc.) zugewiesen werden können, während Generatoren (intern) zusätzlichen Aufwand erfordern, um diesen "Big-bund" -Ansatz zu vermeiden, indem der Stack-Frame-Zustand beibehalten wird, um die Wiederaufnahme der Ausführung zu ermöglichen.

Ob ein List-Approach oder Generator-Ansatz schneller ist in einem echten Programm hängt von der genauen Speichersituation, einschließlich Fragmentierung, die in einem "Mikro-Benchmark" etwa unmöglich ist zu reproduzieren. IOW, am Ende, wenn Sie wirklich Wert auf Leistung legen, müssen Sie Ihr aktuelles Programm (e), nicht nur "Spielzeug" -Mikro-Benchmarks, im allgemeinen Fall sorgfältig vergleichen (und separat profilieren).

+0

1+ umkehren. Es kann auch angemerkt werden, dass Generatoren in vielen Fällen aufgrund ihrer stromähnlichen Natur weniger Speicher benötigen. Erwägen Sie, jede Zeile in einer Datei zu einer Liste zu lesen und vergleichen Sie diese Zeile mit jeder Zeile, um damit zu arbeiten und sie zu verwerfen. – Skurmedel

3

Soweit ich mich erinnern kann, muss für jedes Ergebnis ein Generatorrahmen aktiviert werden, während das Listenverständnis den einen Aktivierungsrahmen verwendet. Die inkrementellen Kosten in der Listenkomprimierung sind die zusätzlichen Kosten des Speichers - Verweise auf int in Ihrem Fall. Die Beziehung kann sich möglicherweise umkehren, wenn jedes Element eine neue Instanz ist und mehr Speicher belegt.

Update: Nach dem Test tat es

~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum([oint(1) for i in L if i & 1])" 
10 loops, best of 3: 414 msec per loop 

~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum(oint(1) for i in L if i & 1)" 
10 loops, best of 3: 392 msec per loop