Vom Overhead der Kommunikation …

Grundidee
Auf der Suche nach Flaschenhälsen und Ineffizienz in einem meiner Codes fiel mir Folgendes auf:

1. es gibt eine einfach Berechnung, die immer wieder verwendet und häufig aufgerufen wird
2. Auslagerung dieser Berechnung in eine statische Methode einer Bibliothek macht also Sinn

Dies führt also zu einem Aufruf der Art Bibliothek.Mathe.degToRad (x) statt die Lösung des Problems jedes Mal aufs Neue an Ort und Stelle zu formulieren. Kurz: der Gedanke der Wiederverwertung. Aufbauend darauf der Gedanke der objektorientierten Programmierung, welche einen jede Komplexität beherrschen läßt.

Problem 1 – Instanztiefe
Mit jeder Instanztiefe wird der Aufruf brutal langsamer. Sprich wenn wir die Methode endlos von Instanz zu Kind-Instanz verlinken, dann wird diese Methode niemals fertig. (!) Das klingt jetzt trivial, aber das ist es nicht: die Instanz-Struktur beeinflußt im hohen Maße die Geschwindigkeit einer Methode. Sicher kann man an dieser Stelle dafür sorgen, daß man flach in der Instanzstuktur bleibt (egoistische Diktator-Instanz) …, aber wer will denn das? Widerspricht das nicht dem objektorientierten Streben eines jeden, der Komplexität durch Greifbarkeit und Analogien zu meistern versucht? Oder wiederverwendbare Bibliotheken anzulegen, die in Grenzfällen dafür sorgen, daß sie gar nicht nutzbar sind aufgrund ihrer Kapselung und dem damit verbundenen „Kommunikations-Overhead“ …

Problem 2 – statische Fremdmethoden (Bibliotheken)
Der Geschwindigkeitseinbruch bei statischen Aufrufen schon bei Methoden 1. Ordnung ist fast unglaublich. Es macht einen Riesenunterschied, ob ich 10.000.000 Mal eine eigene Methode aufrufe oder eine statische Fremdmethode.

Ein Beispiel:
10.000.000 Mal 1 * 1 berechnen = 49 ms
10.000.000 Mal eine statische Methode aus einer Bibliothek aufrufen, die 1 * 1 berechnet = 2534 ms (um den Faktor 50 langsamer!)

Jetzt stellen wir uns die Berechnung mal komplizierter vor: in der Berechnung wird zum Beispiel die Konstante PI aus der AS3-Standardklasse Math benutzt und schon sind wir wieder eine Schritt weiter im Sumpf des Zeitverbratens …

Bedeutung
Es ist logisch, daß 1 * 1 schneller ist als pow (1, 2), weil beim Methodenaufruf noch diverse Checks durchgeführt werden, die wir in unserem trivialen Fall gar nicht benötigen. Bedeutend ist aber, das jeder Versuch der Kapselung Geschwindigkeit kostet … – und zwar in einem Maße, daß man gleich wieder geneigt ist, stupide Kilometer-Codes zu erzeugen, wo alles flach hintereinander abgearbeitet wird. Ohne Methodenaufrufe und Kapselung …

Vielleicht nichts, was einen Programmierer überraschen sollte. Mich überraschte nur, daß dies gleich 50% der kompletten Rechenzeit beansprucht. Vielleicht betrifft dies auch nur AS3 & Flash so extrem? Abhängig vom Grad der Kompilierung? Oder bin ich einfach zu objektorientiert? Vielleicht übertreibe ich auch maßlos? Entweder man programmiert also wie eine Maschine (alles hintereinander weg) oder man hat einen Kompiler, der die Nachteile der Objektorientierung ausbügeln kann. Aber ist der Kompiler zur Laufzeit wirklich noch relevant? Wenn das Programm im Speicher ist, dann gibt es doch eigentlich nichts mehr zu optimieren … – und trotzdem kleckert die Zeit weg, als würde man eine Wand dressieren wollen.

Code
Hier ein kurzer Prüfcode, der das Problem konkret macht für die, die es interessiert …
Das Ergebnis ist einfach, daß mit steigender Tiefe die Abarbeitungszeit für die gleiche Arbeit immer weiter ansteigt. Man könnte meinen, daß dies durch die Prüfung (oChild ?) passiert: sicher verbraucht dies Rechenzeit, aber nicht 25% (variiert natürlich mit dem Rechenaufwand, den eine Methode selber verursacht) mit jeder weiteren Instanztiefe. Wenn ich darüber nachdenke wohl ein eher schlechtes Beispiel, aber wer das selbst mal ausprobiert, wird sehen, wo es brennt …

Analyse-Beispiel

Kommentare

  • Marc 17.1.2008, 13:22

    Hi Thomas,
    hmm..das ist ja schon extrem und ich kann mir nur vorstellen, daß hier beim Compiler mächtig geschlampt wurde und Aufrufe der form adresse1->adresse2->adresse3->usw.->adresseXYZ
    nicht gleich in einen aufruf adresse1->adresseXYZ umgesetzt werden? Vielleicht gibts da noch eine Compiler-Option mit der man das beschleunigen kann, vielleicht liegts auch am Innenleben des Flash-PlugIns?

    Bei der EchtzeitSoftware-Entwicklung kann man sowas nämlich nicht beobachten!!

  • alex 20.3.2008, 23:46

    Overhead durch Funktionen sind nichts neues. Bei C++ gibt es dafür inline Funktionen. Das ist ein Hinweis für den Compiler der Art: Hey hier ist zwar eine Funktion, die tut aber so wenig, schau mal ob du das nicht mit weniger Aufwand auf den Stack legen kannst..

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.