Kleine Optimierungen unter Freunden

So, Ihr Programm funktioniert, gröbere Fehler sind ausgemerzt, aber die Geschwindigkeit könnte einfach besser sein?

Da mag ich Ihnen drei Dinge ans Herz legen. Zum einen die Lektüre dieses Artikels zur Gestaltung von Benutzerinterfaces. Oftmals muss nämlich das Programm gar nicht beschleunigt werden, sondern die optische Reaktionszeit auf Benutzereingaben.

Wenn aber die UI flink ist, es trotzdem keinen Spaß macht, auf die endgültige Ausgabe zu warten? Dann ist es Zeit, den Profiler anzuwerfen:

  • Klicken Sie im Menü von Xojo unter „Project“ die Zeile „Profile Code“ an und lassen Sie Ihr Programm kompilieren.
  • Führen Sie ein paar der Aktionen aus, die Sie als verbesserungswürdig empfinden. Verzweifeln Sie nicht! Alles dauert bedeutend länger als sonst, weil jede Methode mitprotokolliert und ihre Ausführungszeit aufgezeichnet wird.
  • Beenden Sie das Programm über die Menüzeile oder den Tastaturbefehl zum Beenden des Programms (schießen Sie es nicht im Debugger ab).

Mit der Rückkehr in die Xojo-IDE wird jetzt automatisch in den Profiler umgeschaltet und Sie sehen das Ergebnis Ihres Probelaufs – das sieht in etwa so aus:

Profiler 1

Uff! Was fängt man mit diesen Informationen an?

Sie sehen hier die Methoden und Events Ihres Programms, die während der Ausführung aufgerufen wurden.
Der Spalte „Called“ können Sie entnehmen, wie oft jeder Programmblock aufgerufen wurde.
In der nächsten Spalte die Ausführungszeit, die diese Aufrufe benötigt haben, in Millisekunden, und die letzte Zahlenkolumne stellt dar, wie lange ein jeder Aufruf im Schnitt benötigt hat.

Es gibt eine einfache Faustregel:

Schleifen, die besonders oft aufgerufen werden, sollten möglichst schnell funktionieren.

Logisch, oder? In Programmschleifen kumuliert die Ausführungszeit; eine minimale Verbesserung der Zeit, die für einen Durchgang benötigt wird, kann erhebliche Geschwindigkeitsvorteile bringen, was die Gesamtperformance ausmacht.

Ich habe hier in diesem Beispiel eine Zeile hervorgehoben, deren Code besonders oft aufgerufen wurde. Wie Sie sehen, ist dies der CelltextPaint-Event einer Listbox (und Sie sehen zwei Zeilen darunter, dass der CellBackgroundPaint-Event ebenso häufig aufgerufen wurde – ist ja logisch, beides bezieht sich auf das Steuerelement PLlistbox im ContainerControl PhoneLogControl).

Ihnen sind die Aufklappdreiecke am Zeilenbeginn aufgefallen. Klicken wir mal auf eines …

… und die Methode klappt auf und gibt Ihre einzelnen Bestandteile preis:

Profiler 2

Seien Sie nicht verwirrt, dass die CellTextPaint-Zeile nun eine viel geringere Ausführungszeit ausweist. Solange die Methoden zugeklappt sind, zeigen Sie die Summe aller Ausführungszeiten an, die in Ihrer Regie laufen. Im aufgeklappten Zustand sehen Sie, wie es um die einzelnen Untermethoden bestimmt ist. Der eigentliche Paint-Event schlägt also viel geringer zu Buche als die Aufrufe und Umrechnungen, die mit der Cocoa.NSColor-Methode zusammenhängen.

Nebenbei: Wie Sie sehen, haben viele der Untermethoden nun ihrerseits Aufklappdreiecke. Sie können auch hier genau hineinzoomen, um die größten Zeitfresser genau zu bestimmen.

In diesem Fall ist es aber so: Die Cellpaint-Events werden aufgerufen, während eine Listbox auf dem Bildschirm aufgebaut wird. Sie sind, wie die Namen vermuten lassen, jeweils für die Text- und Hintergrundausgabe zuständig. Ich greife hier immer wieder auf Systemfarben zurück, um meine Fenstergestaltung dem System anzupassen – sollte sich Apple z.B. zur Einführung eines Dark Mode auch für die Fenster entscheiden oder der Anwender ein Skin-Programm verwenden, um Fenster und Farben umzugestalten, wird dies von meinem Programm berücksichtigt.

Im CellTextPaint-Event meiner Listbox gibt es u.a. dieses Stückchen Code:

If me.Selected(row) = true then
g.ForeColor = NSColor.TextBackgroundColor
else
g.ForeColor= nscolor.TextColor
end if

Nicht nur, dass das relativ unelegant ist – IF kann auch als Operator verwendet werden, was ebenfalls Vorteile in der Ausführungsgeschwindigkeit bringt –: Ich rufe hier jedesmal eine Funktion auf, die ein neues NSColor-Objekt erzeugt, diesem eine systemdefinierte Farbe zuweist und diese dann auf die Textfarbe überträgt. Dann wird das NSColor-Objekt wieder aus dem Speicher entfernt – siehe die unzähligen Destructor-Aufrufe im zweiten Profiler-Screenshot. Ein wahnsinniger Aufwand (der andererseits zeigt, wie schnell Xojo in der Tat ist), der sich mit nur wenig Mühe beheben lässt.

Das bringt uns nämlich zur zweiten Faustregel für die Optimierung:

Berechnen Sie in Schleifen verwendete Funktionen einmal(!) vor der Ausführung und verwenden Sie in der Schleife ihren gepufferten Wert.

Ich definiere also zwei lokale Variablen des Typs Color für mein Fenster (TextColor und TextBackgroundColor) und weise Ihnen die Farben einmal beim Öffnen zu (auf diese Art würden Farbänderungen des Benutzers während der Laufzeit zumindest bei einem erneuten Fensteröffnen aktualisiert):

TextBackgroundColor = NSColor.TextBackgroundColor
TextColor = NSColor.TextColor

Im Paint-Event optimiere ich die Zuweisung noch:

g.ForeColor = if (me.Selected (row), TextBackgroundcolor, textcolor)

Sie erinnern sich? Das ist besagtes IF als Operator. Im Prinzip genau dasselbe wie der Codeschnipsel oben, aber aktuell (das mag sich mit dem neuen Compiler ändern) ein wenig schneller.

Und wenn wir uns jetzt die Ausführungszeiten anschauen, sieht das Ganze schon viel verträglicher aus. (Achten Sie nur auf die Durchschnittszeiten; ich habe es nicht hinbekommen, die Methode genauso häufig aufrufen zu lassen wie im ersten Durchgang:

Profiler 3Profiler 4

Die Reduktion der Ausführungszeit um mehr als die Hälfte macht sich bei der Häufigkeit der Wiederholungen durchaus bemerkbar – in diesem Fall nicht riesig, aber es trägt zum Gefühl von „Snappiness“ bei, das – wenn alles sonst richtig funktioniert – zur guten Benutzer-Programmbeziehung beiträgt.

In diesem Sinn: Weidmannsheil bei der Jagd auf die Performance-Fresser!

P.S. Die dritte angekündigte Optimierungsmöglichkeit schauen wir uns ein andermal an. Sollen ja Blog-Beiträge sein, keine Bücher.

P.P.S.: Anregung zu diesem Beitrag war dieser Blogbeitrag von Heather Travar, in dem sie ihre Verblüffung beschrieb, in diversen Programmierkursen nie auf die Zeitersparnis durch Puffervariablen aufmerksam gemacht worden zu sein.

Ein Gedanke zu “Kleine Optimierungen unter Freunden

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s