2010-04-10 16 views
11

Was ist der Grund für die befürwortende Verwendung der for i in xrange(...)-Stil Schleifenkonstrukte in Python? Für einfache Integer-Schleifen ist der Unterschied in den Gemeinkosten beträchtlich. Ich führte einen einfachen Test mit zwei Stücken Code:Rational hinter Python ist für Syntax bevorzugt

Datei idiomatic.py:

#!/usr/bin/env python 

M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    for x in xrange(N): 
     for y in xrange(M): 
      pass 

Datei cstyle.py:

#!/usr/bin/env python 

M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    while x < N: 
     while y < M: 
      y += 1 
     x += 1 

Profil Ergebnisse waren wie folgt:

bash-3.1$ time python cstyle.py 

real 0m0.109s 
user 0m0.015s 
sys  0m0.000s 

bash-3.1$ time python idiomatic.py 

real 0m4.492s 
user 0m0.000s 
sys  0m0.031s 

ich kann verstehe, warum die Pythonic-Version langsamer ist - ich stelle es mir vor hat viel damit zu tun, xrange N mal anzurufen, vielleicht könnte das eliminiert werden, wenn es einen Weg gibt, einen Generator zurückzuspulen. Aber warum sollte man bei dieser Differenz in der Ausführungszeit lieber die Pythonic-Version verwenden?

Edit: führte ich die Tests erneut den Code mit Herrn Martelli zur Verfügung gestellt, und die Ergebnisse waren in der Tat besser jetzt:

Ich dachte, ich die Ergebnisse hier aus dem Thread aufzählen würde:

1) Viel Code im Modulbereich ist eine schlechte Idee, auch wenn der Code in einem if __name__ == "__main__": Block eingeschlossen ist.

2) * Merkwürdiger, den Code zu modifizieren, die thebadone meine falsche Version gehörte (lassen y ohne Zurücksetzen) erzeugte wenig Unterschied in der Leistung wachsen, auch für größere Werte von M und N.

+2

Ihr Timing ist fehlerhaft, denke ich. Führen Sie mehrere Versuche, und vielleicht haben einige Berechnungen tatsächlich durchgeführt, um jede mögliche Optimierung aus der Schleife loszuwerden – Yuliy

+0

+1 Sehr interessante Frage. Nachdem ich Martinellis Antwort gelesen habe, ist diese Frage für mich noch interessanter, weil sie die feinen Unterschiede zwischen dem Aufruf eines Stücks Code innerhalb und außerhalb einer Funktion zeigt. – OscarRyz

+1

-1: Da die Basis für die Frage grundsätzlich falsch war, würden Sie bitte die Frage schließen. –

Antwort

22

Hier ist der richtige Vergleich, z.B. in loop.py:

M = 10000 
N = 10000 

def thegoodone(): 
    for x in xrange(N): 
     for y in xrange(M): 
      pass 

def thebadone(): 
    x = 0 
    while x < N: 
     y = 0 
     while y < M: 
      y += 1 
     x += 1 

Alle wesentlichen Code sollte immer in Funktionen sein - hundert Millionen Schlaufen an eines Moduls obersten Ebene zeigt rücksichtslose Missachtung für die Leistung und verhöhnt alle Versuche setzen bei Messung besagte Leistung.

Sobald Sie das getan haben, sehen Sie:

$ python -mtimeit -s'import loop' 'loop.thegoodone()' 
10 loops, best of 3: 3.45 sec per loop 
$ python -mtimeit -s'import loop' 'loop.thebadone()' 
10 loops, best of 3: 10.6 sec per loop 

So richtig gemessen, die schlechte Art und Weise, die Sie befürworten etwa 3-mal langsamer als die gute Art und Weise ist der Python fördert. Ich hoffe, dies bringt Sie dazu, Ihre falsche Befürwortung zu überdenken.

+4

"Alle wesentlichen Codes sollten immer in Funktion sein - hundert Millionen Loops auf der obersten Ebene eines Moduls zu platzieren, bedeutet rücksichtslose Missachtung der Leistung." (Ich verstehe.) Können Sie erklären, warum? –

+6

@Federico, ** Geschwindigkeit **. Variable Get und Set ist in Funktionen hoch optimiert, kann aber nicht auf Modul-Top-Level sein. Z. B. unter der Annahme, dass mein Laptop und Glenn's Maschine gleichwertig sind, sehen Sie aus unseren Zahlen einen Unterschied von Faktor 2, um die Dinge richtig zu machen (wesentlicher Code in Funktionen) gegenüber der völlig falschen Art (wesentlicher Code auf der obersten Modulstufe). Benchmark es selbst! -) –

+1

habe ich gerade getan. Auf Modulebene: 14.3s vs. 36.0s. Lokal zum Funktionieren: 8,6s vs. 18,5s. (!!) Ich wusste es nicht, danke. –

11

Sie zu vergessen Setze y nach der inneren Schleife auf 0 zurück.

#!/usr/bin/env python 
M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    while x < N: 
     while y < M: 
      y += 1 
     x += 1 
     y = 0 

ed: 20.63s nach fix vs. 6.97s xrange

+0

Ich wollte gleich antworten. Mein korrigierter While-Loop-Code lief ungefähr dreimal langsamer als der For-Loop-Code. –

+1

In aller Ernsthaftigkeit, nein. Sprach-Idiome müssen immer eine angemessene Leistung berücksichtigen; Wenn das Xrange-Idiom wirklich * 40 mal langsamer wäre, wäre es ein fehlerhaftes Idiom, das entweder behoben oder nicht mehr verwendet werden sollte.Lesbarkeit ist wichtig, oft sogar auf Kosten von etwas Leistung - aber nicht so viel. –

+0

OK gelöscht meine Antwort auf jeden Fall ... –

3

gut mit Strukturen über Daten Iterieren

Die for i in ... Syntax ist für über Datenstrukturen iteriert. In einer Sprache der unteren Ebene würden Sie im Allgemeinen über ein Array iterieren, das mit einem int indiziert ist, aber mit der Python-Syntax können Sie den Indexierungsschritt eliminieren.

0

Ich habe den Test von @Alex Martelli's answer wiederholt. Die idiomatische for-Schleife ist mal schneller als die while-Schleife:

python -mtimeit -s'from while_vs_for import while_loop as loop' 'loop(10000)' 
10 loops, best of 3: 9.6 sec per loop 
python -mtimeit -s'from while_vs_for import for_loop as loop' 'loop(10000)' 
10 loops, best of 3: 1.83 sec per loop 

while_vs_for.py:

def while_loop(N): 
    x = 0 
    while x < N: 
     y = 0 
     while y < N: 
      pass 
      y += 1 
     x += 1 

def for_loop(N): 
    for x in xrange(N): 
     for y in xrange(N): 
      pass 

auf Modulebene:

$ time -p python for.py 
real 4.38 
user 4.37 
sys 0.01 
$ time -p python while.py 
real 14.28 
user 14.28 
sys 0.01 
1

dies keine ist direkte Antwort auf die Frage, aber ich möchte den Dialog ein wenig mehr auf xrange() öffnen. zwei Dinge:

A. dort mit einem der OP Aussagen etwas nicht in Ordnung ist, dass niemand noch korrigiert hat (ja, zusätzlich zu dem Fehler im Code nicht y Zurücksetzen):

„Ich kann es sich vorstellen, viel mit Aufruf xrange N-mal ....“

im Gegensatz zu herkömmlichen Zählen for Loops, Python ist mehr wie ein Shell foreach ... Schleifen über ein abzählbaren zu tun. daher xrange() heißt genau einmal, nicht "N-mal."

B. xrange() ist der Name dieser Funktion in Python 2. Sie ersetzt und wird umbenannt in range() in Python 3, also beachten Sie dies beim Portieren. Wenn Sie das nicht bereits wissen, gibt xrange() einen Iterator (-ähnliches Objekt) zurück, während range() Listen zurückgibt. da letzteres ineffizienter ist, wurde es zugunsten von xrange(), das mehr Speicher-freundlich ist, veraltet. die Problemumgehung in Python 3, für alle diejenigen, die benötigen, um eine Liste zu haben, ist list(range(N)).

+0

Ich dachte, dass jedes Mal, wenn die innere Schleife alle Iterationen beendet, ein xrange (M) -Objekt wieder definiert wurde, weil AFAIK, Generatoren nicht zurückgespult werden konnten. – susmits

Verwandte Themen