Thread foreach beschleunigen
(22 answers)
Opened by bianca at 2009-01-01 16:40
Bei Benchmarks gibt es drei werte auch wenn du unter Linux zum Befehl mit "time" vorran startest gibt es immer drei Zeitausgaben.
Ein Beispiel: Diese drei Zeitausgaben stecken auch in der Ausgabe des Benchmarksmoduls. Einmal wird dort von "wallclock" gesprochen und einmal von "usr" + "sys". Zuerst "wallclock". "wallclock" oder "wanduhr" ist die reale Zeit die verstrichen ist vom start des programmes bis zum schließen des Programmes. Es ist also eher die "menschliche" zeit die du wahrnimmst die vergangen ist. "wallclock" beim Benchmark Modul ist die angabe "real" beim "time" Befehl. Beachten wir nun mal die ausgabe des befehls "sleep". Der Befehl "sleep" macht letztendlich nichts anderes als die angegebene Zeit in sekunden zu warten. Bei der ausgabe siehst du nun das die "real" zeit auf 3 gesprungen ist. Also drei sekunden. Okay genauer 3.001s Leichte differenzen gibt es immer da die auflösung der zeit nicht so extrem genau ist, weiterhin muss das programm "sleep" auch erstmal geladen, gestartet werden etc. Also der verwaltungsaufwand des OS fällt auch dadrunter. Die angabe "wallclock" gibt dir genau diese Zeit aus bei der Benchmark ausgabe. Als erstes siehst du ja bei deiner benchmark ausgabe folgende Zeilen. "1 wallclock secs" oder "2 wallclock secs" Das jeweils bedeutet 1 oder 2 reale sekunden. Ansonsten ist die auflösung dort nur in vollen Sekunden. Daher die Zeit ist nicht wirklich genau. Wenn du genaue werte haben willst solltest du zumindest benchmarks schreiben die deutlich länger laufen. Wenn du aus der Wallclock Zeit einen nutzbaren Wert haben möchtest. Ansonsten hat die wallclock Zeit aber auch einige Nachteile sowie auch Vorteile. Die Nachteile können bedeuten das die Wallclock Zeit volkommen irrelevant wird, oder sogar die einzige Sinvolle ausgabe ist bei einem Benchmark. Kommen wir zuerst mal zu den Nachteilen. Als erstes ist die Wallclock die echte Zeit die vergangen ist. Das hat einige nachteile den die echte Laufzeit eines Programmes hängt davon ab was dein Computer nebenbei macht. Sagen wir du hast ein benchmark der eine komplexe Berechnung macht die viel CPU Zeit in anspruch nimmt. Du führst den Benchmark zweimal aus. Einmal führst du den Benchmark aus während im Hintergrund nichts läuft, und beim zweiten mal führst du deinen Benchmark aus allerdiengs hast du eine weitere große Berechnung die im Hintergrund läuft. Von mir aus machst du gerade noch Videobearbeitung oder sonst etwas parallel. Das Resultat wäre also das du für den gleichen Benchmark mit dem gleichen Code unterschiedliche Wallclock Zeiten bekommst. Teilweise absolut gravierende Zeitunterschiede. Zum Beispiel braucht dein Code einmal nur 2 Sekunden, und dein Benchmark während deiner Videobearbeitung brauchte auf einmal 10 wallclock sekunden. Das bedeutet nicht das dein erster durchlauf deswegen 5 mal schneller war, oder dein zweiter durchlauf 5 mal langsamer. Da es nur die Zeit angibt wie lange dein Code/Programm lief, ohne zu beachten was Parallel zu deinem Programm lief. Gerade wenn du zwei unterschiedliche Programmecodes benchmarkst solltest du also immer darauf achten das deine Programmumgebung die selbe ist. Idealerweise macht man immer mehrere benchmarks und streicht z.B. den besten und den schlechtesten benchmark weg, und rechnet über die verbleibenden einen Mittelwert. Ansonsten kommen wir nun zur usr/sys angabe mit der du wahrscheinlich erstmal denken wirst das mit dieser angabe die "wallclock" zeit unwichtig wird. Um es erstmal grob zu sagen. Wenn du die werte für "usr" und "sys" zusammen addierst dann bekommst die die Zeit heraus die dein Prozessor gebraucht hat um diese aufgabe zu bearbeiten. Also nicht die real vergangene Zeit die verstrichen ist. Sondern die Zeit die dein Prozessor verbraucht hat für diese aufgabe. Dies hat den Vorteil das selbst wenn du zum Beispiel nebenbei deine CPU auslastest und die wallclock zeit auf das fünffache steigen sollte die cpu zeit die gleiche sein wird wie beim vorherigen durchlauf wo dein Computer nichts parallel getan hat. Die Real Zeit ist ja nur deswegen angestiegen weil dein OS deinem Prozess nicht mehr soviel CPU Zeit zuweisen konnte und deswegen die realzeit gesteiegen ist. Während die wirklich CPU zeit ja identisch blieb. Um nochmal die beiden Zeilen als ausgabe vorzulesen. Code: (dl
)
1 SCHWARTZ: 1 wallclock secs ( 1.30 usr + 0.02 sys = 1.31 CPU) @ 76.16/s (n=100) Der Benchmark SCHWARTZ lief also 1 wallclock sekunde, der benchmark SUBSTR lief 2 wallclock sekunden. Da leider nur eine auflösung von vollen Skunden hier existiert könnte es auch mehr sein. Ich weiß gerade eben nicht ob er aufrundet oder abschenidet. Allerdiengs spielt das auch wenig eine Rolle beo solchen Werten. Wie gesagt wegen der Auflösung von Sekunden müsstest du Benchmarks machen die deutlich länger laufen als ein paar sekunden am optimalsten z.B. Minuten. In Klammern dahinter siehst du nun die CPU Zeit. Code: (dl
)
( 1.30 usr + 0.02 sys = 1.31 CPU) Daher der erste Benchmark hat 1.31 CPU Sekunden verbraucht. "usr" und "sys" bedeutet letztendlich wo der CPU seine Zeit verbraucht hat. Von "usr" redet man im "userspace" und von "sys" redet man im "Kernelspace" des Betriebssystem. Inwiefern einem diese aussagen helfen sollen ist mir allerdiengs etwas selber unschlüssig. Das einzige was du wohl daraus interpretieren kannst ist das du am kernelspace selber wenig optimieren kannst, da du hier primär OS Funktionen aufrufst und im Userspace läuft eher dein eigener Code und hier hast du evtl. noch mehr Optimierungsbedarf. In der Regel beachtet man aber nur die zusammen addierte Zeit. Hier 1.31 CPU Sekunden. Ganz zum Schluß kommt dann eine auswertung. Code: (dl
)
@ 76.16/s (n=100) Das (n=100) am ende sagt aus das es 100 Itterationen gab. Mit der CPU zeit und 100 Itterationen könntest du nun ausrechnen wieviele durchläufe pro sekunde bei den einzelnen durchläufen möglich sind. Allerdiengs musst du das nicht selber berechnen sondern es wird schon ausgegeben. Beim ersten durchlauf kommt also heraus das 76.16 aufrufe pro sekunde möglich sind, und beim zweiten 103.31 durchläufe pro sekunde. Man kann also zum Beispiel hierraus schließen das die zweite Lösung möglicherweise schneller ist. Für die Berechnung der Durchläufe pro sekunde wird übrigens auch die CPU Zeit genommen und nicht die wallclock sekunden. Wenn du bis hierhin gelesen hast wirst du wahrscheinlich erstmal denken das die CPU Zeit die "bessere" wahl der beiden ist und die wallclock Zeit wahrscheinlich irrelevant ist. Leider ist es nicht so simpel. Auch deine ergebnisse der Benchmarks sind interessant. Code: (dl
)
1 SCHWARTZ: 1 wallclock secs ( 1.30 usr + 0.02 sys = 1.31 CPU) @ 76.16/s (n=100) SCHWARTZ lief also 1 wallclock sekunde und hat 1.31 CPU Zeit Sekunden benötigt und kommt auf 76 Durchläufe pro sekunde. Ergebniss zwei sagt aus das 103.31 durchläufe pro sekunde mötlich sind bzw der Code nur 0.97 CPU Zeit Sekunden lief also weniger als SCHWARTZ Trotzdem lief dein Code länger als die SCHWARTZ Lösungen mämlich mit 2 wallclock sekunden. Letztendlich spielt es keine Rolle ob nun einfach das ende abgeschnitten wird bei der wallclock anzeige oder auf/abgerunden wird. Das ergebnis besagt das SCHWARTZ kürzer lief, obwohl SUBSTR eigentlich schneller sein sollte. Erste Vermutung: Während du den Benchmark gelaufen lassen hast, hat deine CPU auf einmal während SUBSTR lief mehr im Hintergrund gemacht als bei SCHWARTZ. Dadurch lief es an reale wallclock zeit länger. Ansonsten möchte ich noch auf etwas anderes Hinweisen. Ein anderer Punkt den du dir stellen kannst und vielleicht mal bei anderen Benchmarks sehen wirst. SCHWARTZ hat 1.31 CPU Zeit Sekunden gebraucht aber nur 1 Wallclock sekunde. Wie ist das Möglich? Wenn die CPU also 1.31 Sekunden gebraucht hat, wie kann dann die echte vergangene Zeit kürzer sein? Muss nicht die reale Zeit immer länger sein als die CPU Zeit? Kurz gesagt jaein. Wenn du lediglich einen Single Core CPU hast dann "ja". Ansonsten "nein". Denn wenn du z.B. mehrere CPU Cores hast kann es durchaus sein das die CPU Zeit 2 Sekunden waren. Allerdiengs nur 1 wallclock sekunde am ende dabei heraus kommst. Auf solch einem ergebniss kommst du nämlich wenn dein Computer zwei CPUs hat und diese beiden für 1 Sekunde vollständig ausgelastet werden. Dann hast du 2 CPU Sekunden aber nur 1 wallclock sekunde. Es kann also Grundsätzlich so sein das die CPU Zeit höher ist als die wallclock zeit, allerdiengs nur wenn du mehrere CPU Kerne hast und wenn deine Umgebung diese auch ausnutzt. Auf Perl trifft das allerdiengs nicht zu da die Implementierungen der Sprache selber nicht automatisch Aufgaben auf mehrere CPU Kerne aufteilen. Bei Sprachen wie Haskell und deren Implementierung wie der GHC ist das allerdiengs der Fall und bei Perl 6 und implementierungen wie Rakudo soll das später auch der Fall sein. Das heißt bei solchen Sprachen könnte es durchaus sein das die CPU Zeit höher ist, und nach der CPU Zeit die Durchläufe pro sekunde langsamer ist. Allerdiengs trotzdem die langsamere lösung die schneller ist. Nur um ein Beispiel zu nennen. Nehmen wir an Lösung X benötigt zwar 1.5 CPU Zeit sekunden und Lösung Y nur 1 Sekunde. Allerdiengs kann Lösung X mehrere CPU Kerne Nutzen, Lösung Y aber nicht. In diesem beispiel würde ich dann sagen das Lösung X schneller ist. Hast du nämlich zwei CPU Kerne würde die aufgabe nur 0.75 wallclock sekunden dauern (1.5 cpu zeit / 2 kerne = 0.75) . hast du vier CPU Kerne dann würde es nur noch 0.375 wallclock sekunden dauern etc. Lösung X nutzt also deine Hardware besser aus. Während Lösung Y immer auch mindestens 1 wallclock sekunde dauern wird. Selbst wenn du 2, 4, 8 oder mehr kerne hast. Glücklicherweise oder unglücklicherweise (je nachdem wie man es nimmt (ich sehe es unglücklicherweise)) hat Perl 5 und die Implementierung selber weder die Möglichkeiten noch nutzt es automatisch mehrere Kerne. Daher fällt soetwas "manchmal" weg. Manchmal auch nur dann da du ja in Perl schon forken kannst und mehrere Prozesse erstellen kannst und du schon Lösungen Programmieren kannst die mehrere kerne ausnutzen. Soetwas musst du im Hinterkopf behalten. Eine Reine aussage Lösung XYZ hat weniger CPU Zeit gekostet und ist deswegen schneller ist jedenfalls schwachsinnig du musst halt noch die umstände beachten. Da es allerdiengs derzeit keine Perl 5 Implementierung gibt die mehrere Kerne automatisch nutzt und du auch so nicht in der Programmierung forkst oder ähnliches fällt dieser Punkt weg. Zumindest siehst du an diesem Beispiel das es nicht immer so einfach ist wallclock zeit gegen CPU Zeit abzuschätzen. Gerade wenn du fremde Programme Benchmarkst z.B. mit dem "time" Befehl unter einer GNU/Linux Distribution musst du soetwas im Hintergrund beachten. Ansonsten kommt noch etwas viel wichtigeres was die Wallclock Zeit beinflusst nicht aber die CPU Zeit und das auch bei Perl und allen Implementierungen der Fall ist. Nämlich I/O. Sprich alles was du an I/O machst, sprich etwas aus einer Datei lesen, in einer Datei schreiben, Datenbank Lesen/Schreiben, ausgaben machen etc. dort hast du das Problem das Buffer im Spiel sind, und viel mehr noch, I/O ist nicht so schnell wie deine CPU. Das hat ein Effekt den ich mit einem simplen beispiel im Kopf durchgehen möchte. Stell dir vor du schreibst ein Programm das eine 2 GiB Datei Zeilenweise durchgehen soll und diese lediglich ausgeben soll. Dein Programm würde ständig neue Zeilen aus der Datei anfragen jedoch wäre deine I/O einfach nicht schnell genug so schnell neue Zeilen zu liefern wie du diese wieder ausgegeben hast. Der Effekr ist das du eine neue Zeile aus einer Datei anfragst. Und dein Programm "wartet" dann solange bis es eine neue Zeile empfangen hat. Oder noch genauer. Dein Programm wartet nicht das OS teilt die CPU Zeit stattdessen einem anderem prozess zu. Daher in der zeit wo noch auf die neue Zeile aus der Datei gewartet wird, schläft dein Programm und das OS führt etwas anderes aus. Das Resultat ist aber das deine wallclock Zeit natürlich steigt da dein Programm ja trotz alledem warten musst da die Festplatte nicht schnell genug Daten liefert allerdiengs deine CPU Zeit nicht weiter steigt da es nichts für den CPU zu tun gibt auser zu warten bis eine neue Zeile eingetroffen ist. Aus diesem grund ist die wallclock Zeit auch unheimlich wichtig, aber eben nur dann wenn deine Lösung z.B. mehrere CPU Kerne nutzt oder dann wenn du z.B. I/O nutzt. Allerdiengs kann es auch andere Faktoren geben wo einfach nur gewartet wird ohne CPU zeit zu verschwenden. Ein Beispiel was ich schon gezeigt habe war der "sleep" befehl weiter oben. Dieser hatte "3 wallclock" sekunden allerdiengs 0 CPU zeit. Da eben fürs warten keine CPU Zeit benötigt wird trotz alledem natürlich echte Zeit verstreicht. Wichtig können solche Punkte werden wenn du wie gesagt etwas mit I/O machst. Wenn du Beispielsweise zwei Lösungen hast und eine Lösung deutlich mehr I/O verursacht dafür aber wenig CPU Zeit ist diese Lösung eher schlechter als eine Lösung die mehr CPU Zeit kostet allerdiengs I/O sehr wenig beanspraucht. CPU Zeit skaliert nämlich immer noch besser und die Geschwindigkeit der CPUs (oder heutzutage die anzahl der CPUs) steigt immer noch rasanter als es I/O tut. So wäre eine Lösung die mehr CPU Zeit benötigt und auf dem Papier nach CPU Zeit rechnung langsamer ist aber nach wallclock rechnung schneller ist zum beispiel deutlich besser. Was übrigens auch zu I/O gehört ist wenn dein Programm so viel Speicher benötigt das es anfängt zu swappen und deine Festplatte nutzt das führt dazu das die wallclock zeit extrem ansteigt, während sich die CPU zeit mehr oder weniger nicht verändert. Auch hier kann es durchaus so sein das eine Lösung besser ist die sehr wenig Speicher benötigt, die nicht anfängt zu swappen aber dafür mehr CPU Zeit benötigt. Und wenn wir uns schon über die Wahl der Lösung unterhalten so ist es genauso wichtig wie einfach/schwer die Lösung ist. Eine Lösung die unheimlich Schwer nachzuvollziehen ist auch wenn sie vier mal schneller ist, ist nicht immer zu bevorzugen. Zum Beispiel dann nicht wenn diese Lösung so selten aufgerufen wird das sie von der kompletten Programmlaufzeit oder Modullaufzeit wie auch immer absolut keinerlei zeit beansprucht. Oder die Funktion sowieso schon schnell genug ist und nicht oft genug aufgerufen wird so das sie ebenfalls keine Programmlaufzeit beansprucht. Und manchmal wiederrum kann es auch besser sein wenn du eine einfachere/langsamere Lösung nutzt und stattdessen ergebnisse einfach cachest. So gewinnst du Lesbarkeit und einfachheit (was die Korrektheit fördert) im austausch gegen etwas Speicher. Für das Cachen von Funktionen z.B. kann dir auch das Modul "Memoize" helfen wodurch du ein Cache mit minimalen aufwand einbaust. Stellt sich nur die Frage wie du heraus bekommst welche Sachen wichtig sind. Ganz einfach, indem du Profilst. Indem du Devel::NYTProfnimmst und somit heraus findest welche Sachen wichtig sind und welche Sachen sich überhaupt lohnen. Devel::NYTprof nutzt übrigens auch die Wallclock Zeit für das Profilen und nicht die CPU Zeit, da du in der Regel Wissen willst wenn du z.B. Datenbankabfragen machst und dein Programmcode 30 Sekunden auf das ergebnis einer Datenbankabfrage gewartet hat weil das generierte SQL schlecht war, du fehlende indexe in der Datenbank hattest oder du generell zu viele Datenbankabfragen hast etc. Von daher auch wenn "pq" jetzt anfangen mag zu schreien. Vergiss das Benchmarken da es total unsinnig ist es möglicherweise mehr Zeit verschwendet als es dir bringt und Profile stattdessen nur. Beim Profilen siehst du ja ebenfalls ob eine Optimierung etwas gebracht hat wie beim Benchmarken. Allerdiengs alles mehr in einer realistischen umgebung die auch wirkklich auftaucht. Zum Schluß hoffe ich das du nun die bedeutung der ausgabe verstanden hast und du nun vom korrekten auswerten des Benchmarkens erschrocken bist und du es von nun an nie wieder machst und nur noch Profilst. ;) Nicht mehr aktiv. Bei Kontakt: ICQ: 404181669 E-Mail: perl@david-raab.de
|