Seite 1 von 1
Zählschleifen for oder while?
Verfasst: Freitag 20. Dezember 2002, 18:12
von Dookie
Hallo zusammen,
ich habe mir gerade mal gedanken gemacht, was wohl schneller ist, eine Zählschleife mit while oder mit for?
Um eine Antwort zu bekommen, habe ich folgendes kleines Script geschrieben.
Code: Alles auswählen
#!/usr/binenv python
from time import clock
max = 1000000
print "Teste Whileschleife"
print "i = 0"
print "while i < %d:" % max
print " pass"
print " i = i+1"
a = clock()
i = 0
while i < max:
pass
i = i+1
b = clock()
lzw = b-a
print "Laufzeit = %.3f\n" % lzw
print "Teste Forschleife"
print "for i in range (%d):" % max
print " pass"
a = clock()
for i in range(max):
pass
b = clock()
lzf = b-a
print "Laufzeit = %.3f\n" % lzf
if lzw > lzf:
print "Eine Forschleife ist %.2f mal schneller als eine Whileschleife" % (lzw/lzf)
else:
print "Eine Whileschleife ist %.2f mal schneller als eine Forschleife" % (lzf/lzw)
print
Ich denke mal das Ergebnis spricht für sich, besonders interessant sind auch die Ergebnisse verschiedener Pythonversionen, so ist Python2.2 bei mir (Linux) 10-20% schneller als Python2.1, auch der Unterschied zwischen for und while ist deutlich ausgeprägter.
Gruß
Dookie
Verfasst: Freitag 20. Dezember 2002, 20:41
von Beyond
Bei mir ergibt sich:
"Eine Forschleife ist 2.38 mal schneller als eine Whileschleife"
Das ist interessant. Aber vmtl. liegt, das daran, daß das Python-range-Objekt in C programmiert ist. Und for-Schleifen auch optimiert für derartige Objekte sind.
Verfasst: Freitag 20. Dezember 2002, 22:36
von hans
Also auf einem Linuxservechen ergibt sich ein Geschwindigkeitsvorteil von 1,78 bis 1,85 für die for Schleife.
Interessanterweise wird die Anweisung i = i+1 scheinbar geringfügig langsamer ausgeführt als i += 1
Hans
Verfasst: Samstag 21. Dezember 2002, 00:28
von Dookie
Hallo nochmal,
bei mir ergibt sich mit Python2.1 auch ein Geschwindigkeitsvorteil von 1.7 - 1.8, mit Python 2.2 ist die For-Schleife 2x so schnell und insgesammt ist Python 2.2 nochmal um etwa 10% schneller als 2.1. ich habe i = i+1 gewählt, damit das Script auch mit Pythonversionen <2.x läuft.
Von Vorteil ist bei der While-Schleife der geringere Speicherverbrauch, der aber erst bei sehr großen Werten zum Tragen kommt (bei mir steigt Python bei 10000000 bei der For-Schleife aus).
Gruß
Dookie
Verfasst: Samstag 21. Dezember 2002, 11:46
von Milan
lool... ich hab gerade mal xrange getestet. xrange ist
sehr speichersparend, weil es immer erst das nächste Schleifenglied ausrechnet, was gebraucht wird. Also eigentlich wie x+1 bei jedem Aufruf, was ja langsamer sein müsste, dafür aber Zahlen bis zum ende des Arbeitsspeichers in For-Schleifen passieren lassen kann. Und siehe da, komischerweise ist es um nocheinmal 30% schneller. Deswegen sind bei mir for-schleifen auf jeden Fall erste Wahl. Interessant ist auch, das es auch für Dateien ein xreadlines gibt, was man an for-Schleifen übergeben kann. Damit kann man auch große Dateien genauso schnell bearbeitenund muss nich immer eine Zeile mit readline abarbeiten und dann beim nächsten Durchlauf wieder readline nehmen.
Verfasst: Samstag 21. Dezember 2002, 13:38
von Beyond
Das ist ja interessant.
Ich hatte gedacht, daß in den neueren Python-Versionen xrange und range das selbe ist. Das alte range macht ja auch nicht viel Sinn -- Oder?
Ihr müßt aber den Test mehrfach ausführen. Oder das Programm so abändern, daß die beiden Zählschleifenversionen abwechselnd mehrfach ausgeführt werden. Die Einzelmessung schienen bei mir einer großen Statistischen Schwankung zu unterliegen.
Ansich könnte man da auch eine schöne Fehlerrechnung machen ...
cu beyond
Verfasst: Samstag 21. Dezember 2002, 15:02
von hans
Die Einzelmessung schienen bei mir einer großen Statistischen Schwankung zu unterliegen.
Ich denke, da gibt's mehr Unterschiede zu beachten, als auf den ersten Blick so zu vermuten ist.
- Wie ist die Prozessorauslastung
- welches OS
- welche Hintergrundprozesse
- Prioritäten der Prozesse
- Speicher
- RAM-Disk
- etc., etc, etc.
Während ich getestet habe, lief gerade Mozilla 1.2 und XMMS versorgte mich mit Sound aus MP3. ICQ war glaube ich auch am werkeln. Eigentlich nix besonderes für einen 433 Celeron mit 192 MB RAM, SuSE 8.1 und KDE 3.
Linux hat ja bekanntlich nie genug Speicher. Was "übrig" ist, wird halt zum Cachen verwendet.
Hans
Verfasst: Samstag 21. Dezember 2002, 16:33
von Milan
Beyond hat geschrieben:Das alte range macht ja auch nicht viel Sinn -- Oder?
das range macht vielleicht im Vergleich zu xrange in schleifen weniger sinn, aber range erzeugt im Gegensatz zu xrange eine komplette liste. man kann das also auch zum initialisieren von Variablen nehmen. Überflüssig ist es auf jeden Fall nicht.
xrange erzeugt ein iterierbares Objekt, also speziell für for-schleifen ausgelegt. Da aber wie gesagt für das iterieren nur 1 Element pro Durchlauf benötigt wird, kann da ja am Anfang eines neues Durchlaufes wieder neu erzeugt werden. Ich nehm mal an, dass x-Objekte in C geschrieben sind, weil sonst müssten sie wirklich langsamer als range-schleifen sein, wo ja immer das nächste schon vorliegt.
für kleine Sachen tuts ja auch ein range, man braucht ja nicht gleich mit Kanonen auf Spatzen zu schießen, wenn man es schnell machen will.
Verfasst: Samstag 21. Dezember 2002, 17:34
von Beyond
Wieder mal was neues kennengelernt.
range macht also eine echte -- sprich beschreibbare -- Liste.
>>> r=range(10)
>>> r[2]='Hello'
>>> r
[0, 1, 'Hello', 3, 4, 5, 6, 7, 8, 9]
>>>
OK. Dann müssen beide Versionen (range und xrange) auch erhalten bleiben.
cu beyond
Verfasst: Samstag 21. Dezember 2002, 17:36
von Beyond
Aufgrund obiger Unsicherheiten wäre ein längerer Prozess beim Messen der Ausführungsgeschwindigkeit sinnvoll ...
cu beyond
Verfasst: Samstag 21. Dezember 2002, 18:33
von Dookie
Ich hab gerade mal die Suchfunktion von Python.org mit xrange gefüttert, gibt da ein paar sehr interessante Sachen.
Jetzt hab ich mal eine kleine Modifikation bei meinem Script angebracht:
Code: Alles auswählen
#!/usr/binenv python
from time import clock
max = 10000000
def whileloop(n):
i = 0
while i < n:
pass
i = i+1
def forloop(n):
for i in xrange(n):
pass
print "Teste Whileschleife"
print "i = 0"
print "while i < %d:" % max
print " pass"
print " i = i+1"
a = clock()
whileloop(max)
b = clock()
lzw = b-a
print "Laufzeit = %.3f\n" % lzw
print "Teste Forschleife"
print "for i in xrange (%d):" % max
print " pass"
a = clock()
forloop(max)
b = clock()
lzf = b-a
print "Laufzeit = %.3f\n" % lzf
if lzw > lzf:
print "Eine Forschleife ist %.2f mal schneller als eine Whileschleife" % (lzw/lzf)
else:
print "Eine Whileschleife ist %.2f mal schneller als eine Forschleife" % (lzf/lzw)
print
Die Schleifen innerhalb von Funktionen aufzurufen bringt nochmal einen schönen Geschwindigkeitsschub.
Gruß
Dookie
Verfasst: Samstag 21. Dezember 2002, 20:41
von Beyond
Klar wg. des Lookups. Zuerst wird im lokalen Namespace gesucht. Das ist bei so kurzen Funktionen sehr schnell, da der Namespace fast leer ist.
cu beyond
Verfasst: Samstag 21. Dezember 2002, 21:33
von Beyond
Ich habe mal das Programm erweitert. Jetzt wird eine Messreihe durchgeführt, um ein besseres Ergebnis zu erhalten. Ich gehe bei der Fehlerrechnung von einer Gaussverteilung der Messwerte bzw des Fehler aus.
Code: Alles auswählen
from time import clock
from math import sqrt
max = 1000000
measurements= 20
student= { # Student-Funktion/sqrt(n) fuer eine Sicherheit von 68,26 %
4: 0.6,
10: 0.34,
20: 0.23
}
rtWhileArr= range(measurements)
rtForArr= range(measurements)
for iR in range(measurements):
print "MESSUNG " + str(iR+1)
print "Teste Whileschleife"
print "i = 0"
print "while i < %d:" % max
print " pass"
print " i = i+1"
a = clock()
i = 0
while i < max:
pass
i = i + 1
b = clock()
lzw = b-a
print "Laufzeit = %.3f\n" % lzw
rtWhileArr[iR]= lzw
print "Teste Forschleife"
print "for i in range (%d):" % max
print " pass"
a = clock()
for i in range(max):
pass
b = clock()
lzf = b-a
print "Laufzeit = %.3f\n" % lzf
rtForArr[iR]= lzf
lzw= 0
for lzwR in rtWhileArr:
lzw= lzw + lzwR
lzw= float(lzw)/float(measurements)
stAbw= 0
for lzwR in rtWhileArr:
stAbw= stAbw + ((lzwR - lzw)**2)
stAbw= sqrt(float(stAbw)/float(measurements-1))
delta_lzw= stAbw * student[measurements]
lzf= 0
for lzfR in rtForArr:
lzf= lzf + lzfR
lzf= float(lzf)/float(measurements)
stAbw= 0
for lzfR in rtForArr:
stAbw= stAbw + ((lzfR - lzf)**2)
stAbw= sqrt(float(stAbw)/float(measurements-1))
delta_lzf= stAbw * student[measurements]
print "Durschnittliche Dauer einer Whileschleife: " + str(lzw) + " pm " + str(delta_lzw) + " Sekunden"
print "Durschnittliche Dauer einer Forschleife: " + str(lzf) + " pm " + str(delta_lzf) + " Sekunden"
if lzw > lzf:
print "Eine Forschleife ist %.2f mal schneller als eine Whileschleife" % (lzw/lzf)
else:
print "Eine Whileschleife ist %.2f mal schneller als eine Forschleife" % (lzf/lzw)
print
relError= sqrt( ((delta_lzw/lzw)**2) + ((delta_lzf/lzf)**2) )*100
print "Dieses Ergebnis weicht mit einer Sicherheit von 68,26% nicht mehr als " + str(relError) +"% vom wahren Wert ab"
Auf Optimierungen habe ich bewußt verzichtet, da in "normalen" Programmen for-Schleifen auch nicht in Extra-Funktionen verpackt werden.
Viel Spaß,
beyond
Verfasst: Sonntag 22. Dezember 2002, 00:03
von hans
Beyond, ich habe mir mal erlaubt in deinem Sourcecode die print Befehle umzubauen. Jetzt kommt die Ausgabe tabellarisch, was die Vergleichbarkeit der Werte leichter macht.
Code: Alles auswählen
from time import clock
from math import sqrt
max = 1000000
measurements= 20
student= { # Student-Funktion/sqrt(n) fuer eine Sicherheit von 68,26 %
4: 0.6,
10: 0.34,
20: 0.23
}
rtWhileArr= range(measurements)
rtForArr= range(measurements)
print "\n\n\n\n\n\n\n\nTeste Whileschleife"
print "i = 0"
print "while i < %d:" % max
print " pass"
print " i = i+1\n"
print "Teste Forschleife"
print "for i in range (%d):" % max
print " pass\n\n\n"
print "MESSUNG %3s %9s %9s" % ("Nr", "WHILE", "FOR")
for iR in range(measurements):
a = clock()
i = 0
while i < max:
pass
i = i + 1
b = clock()
lzw = b-a
rtWhileArr[iR]= lzw
a = clock()
for i in range(max):
pass
b = clock()
lzf = b-a
print "MESSUNG %3d %9.3f %9.3f" % (iR+1, lzw, lzf)
rtForArr[iR]= lzf
lzw= 0
for lzwR in rtWhileArr:
lzw= lzw + lzwR
lzw= float(lzw)/float(measurements)
stAbw= 0
for lzwR in rtWhileArr:
stAbw= stAbw + ((lzwR - lzw)**2)
stAbw= sqrt(float(stAbw)/float(measurements-1))
delta_lzw= stAbw * student[measurements]
lzf= 0
for lzfR in rtForArr:
lzf= lzf + lzfR
lzf= float(lzf)/float(measurements)
stAbw= 0
for lzfR in rtForArr:
stAbw= stAbw + ((lzfR - lzf)**2)
stAbw= sqrt(float(stAbw)/float(measurements-1))
delta_lzf= stAbw * student[measurements]
print "Durschnittliche Dauer einer Whileschleife: %.3f +/- %.3f sek " % (lzw, delta_lzw)
print "Durschnittliche Dauer einer Forschleife: %.3f +/- %.3f sek " % (lzf, delta_lzf)
if lzw > lzf:
print "Eine Forschleife ist %.2f mal schneller als eine Whileschleife" % (lzw/lzf)
else:
print "Eine Whileschleife ist %.2f mal schneller als eine Forschleife" % (lzf/lzw)
print
relError= sqrt( ((delta_lzw/lzw)**2) + ((delta_lzf/lzf)**2) )*100
print "Dieses Ergebnis weicht mit einer Sicherheit von 68,26%% nicht mehr als %.2f%% vom wahren Wert ab" % relError
Das Ergebnis sieht bei mir dann so aus:
Code: Alles auswählen
Teste Whileschleife
i = 0
while i < 1000000:
pass
i = i+1
Teste Forschleife
for i in range (1000000):
pass
MESSUNG Nr WHILE FOR
MESSUNG 1 4.150 2.250
MESSUNG 2 4.220 2.220
MESSUNG 3 4.000 2.240
MESSUNG 4 4.140 2.350
MESSUNG 5 3.960 2.260
MESSUNG 6 4.060 2.250
MESSUNG 7 4.070 2.240
MESSUNG 8 4.040 2.110
MESSUNG 9 4.100 2.240
MESSUNG 10 4.030 2.190
MESSUNG 11 4.120 2.210
MESSUNG 12 4.060 2.260
MESSUNG 13 4.070 2.260
MESSUNG 14 4.110 2.230
MESSUNG 15 4.080 2.490
MESSUNG 16 3.960 2.200
MESSUNG 17 3.990 2.270
MESSUNG 18 4.200 2.260
MESSUNG 19 4.200 2.390
MESSUNG 20 4.250 2.550
Durschnittliche Dauer einer Whileschleife: 4.090 +/- 0.019 sek
Durschnittliche Dauer einer Forschleife: 2.273 +/- 0.023 sek
Eine Forschleife ist 1.80 mal schneller als eine Whileschleife
Dieses Ergebnis weicht mit einer Sicherheit von 68,26% nicht mehr als 1.13% vom wahren Wert ab
hans@poorbill:~/tmp>
Ich trage meine versionen mal nach:
Code: Alles auswählen
Zur Messung wurde Python "2.2.1 (#1, Sep 10 2002, 17:49:17)
[GCC 3.2]" auf "linux2" benutzt
Hans
Verfasst: Sonntag 22. Dezember 2002, 11:59
von Beyond
Cool.
Ich habe oben noch
und unten ein
Code: Alles auswählen
print "Zur Messung wurde Python \"%s\" auf \"%s\" benutzt" % (version, platform)
eingefügt, damit auch gleich die Python-Version und das OS sichtbar sind.
Ich erhalte folgendes Ergebnis:
Code: Alles auswählen
Teste Whileschleife
i = 0
while i < 1000000:
pass
i = i+1
Teste Forschleife
for i in range (1000000):
pass
MESSUNG Nr WHILE FOR
MESSUNG 1 3.720 2.310
MESSUNG 2 3.710 2.140
MESSUNG 3 3.710 2.160
MESSUNG 4 3.710 2.140
MESSUNG 5 3.720 2.150
MESSUNG 6 3.740 2.150
MESSUNG 7 3.710 2.150
MESSUNG 8 3.720 2.130
MESSUNG 9 3.720 2.150
MESSUNG 10 3.710 2.150
MESSUNG 11 3.720 2.150
MESSUNG 12 3.740 2.140
MESSUNG 13 3.730 2.150
MESSUNG 14 3.720 2.140
MESSUNG 15 3.720 2.150
MESSUNG 16 3.710 2.140
MESSUNG 17 3.710 2.150
MESSUNG 18 3.710 2.140
MESSUNG 19 3.720 2.150
MESSUNG 20 3.710 2.140
Durschnittliche Dauer einer Whileschleife: 3.718 +/- 0.002 sek
Durschnittliche Dauer einer Forschleife: 2.154 +/- 0.009 sek
Eine Forschleife ist 1.73 mal schneller als eine Whileschleife
Dieses Ergebnis weicht mit einer Sicherheit von 68,26% nicht mehr als 0.40% vom wahren Wert ab
Zur Messung wurde Python "2.0 (#1, Jan 19 2001, 17:54:27)
[GCC 2.95.2 19991024 (release)]" auf "linux2" benutzt
cu beyond
Verfasst: Sonntag 22. Dezember 2002, 13:43
von Dookie
Bevor das ganze zu einer Forschlacht ausartet
will ich mal zusammenfassen.
For-Schleifen sind effizienter und schneller auch bei Zählschleifen. Speziell für Zählschleifen ist hier die Variante mit xrange(), der Variante mit range(), vorzuziehen. was neben der nochmal etwas schnelleren Ausführung, auch bei großen Schleifen eine deutliche Speicherersparnis ergibt.
Dookie