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. :lol:

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

Code: Alles auswählen

from sys import version, platform
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 :lol: 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