Mario Blättermann
mario.blaettermann@gmail.com 2009, 2012, 2013, 2015
Christian Kirbach
christian.kirbach@googlemail.com 2010

# Was optimieren wir?

Sie sollten bei der Optimierung für GNOME zunächst eines bedenken: Wir versuchen nicht, das Programm zu verbessern, sondern wir versuchen, die Benutzer glücklicher zu machen.

Bessere Programme machen die Benutzer glücklicher, aber es gibt Verbesserungsmöglichkeiten, die sie viel glücklicher machen werden als Andere: Ansprechzeit, Startzeit, leichter Zugriff auf Befehle. Der Rechner sollte auch nicht sofort den Auslagerungsspeicher benutzen müssen, wenn mehr als eine Anwendung geöffnet ist.

Die traditionelle Optimierung umfasst Konzepte wie Prozessorlast, Umfang des Codes, die Anzahl der Mausklicks sowie die Speichernutzung des Programms. Die folgende Auflistung sollte die erste Liste ergänzen, wobei ein grundlegender Unterschied besteht: Ein GNOME-Benutzer schenkt der zweiten Liste keine Beachtung, während der ersten Liste sehr wohl Aufmerksamkeit gewidmet wird. Beim Optimieren von GNOME-Programmen sollen die Prozessorlast, der Speicherverbrauch und all diese Dinge optimiert werden, aber das sind die Bedeutungen, nicht das Endziel. Wir optimieren für Benutzer.

# Ausführung der Optimierung

Aus dem vorigen Abschnitt wurde deutlich: Um etwas optimieren zu können, muss es messbar sein. Sie können Zufriedenheit nicht messen, aber Sie können sehr wohl Startzeiten messen, um sagen zu können, dass Sie sie verbessert haben. Die Zufriedenheit wird dann - hoffentlich - folgen.

Optimierung ist ein Prozess des Messens, der Verfeinerung und erneuten Messens. Daher müssen Sie zunächst einen Weg finden, das zu messen, was Sie optimieren wollen. Idealerweise ist dieser Messwert eine einzige Zahl, beispielsweise die Zeit, die für die Ausführung einer Aufgabe benötigt wird. Dies ist Ihr Maßstab, es ist der einzige Weg festzustellen, ob Sie auf der Gewinner- oder der Verliererseite sind. Es ist ein sehr wesentlicher Unterschied, ob ein Programm schnell sein sollte oder ob es tatsächlich schnell ist.

Sobald Sie einen Ausgangsmaßstab gefunden haben, müssen Sie nun herausfinden, warum Ihr Code seine Aufgabe nicht so gut erledigt, wie er es eigentlich sollte. Es ist verlockend, dies durch eine einfache Sichtung zu tun: Sich den Code anzuschauen und etwas zu finden, dass verbesserungswürdig scheint. Doch Sie werden immer falsch liegen. Der einzig sichere Weg ist es, einen Profiler für eine detaillierte Aufschlüsselung der Programmaktivitäten zu verwenden.

Gewöhnlich ist das Problem auf kleine Code-Abschnitte beschränkt. Wählen Sie den schlimmsten Teil und konzentrieren Sie sich zuerst darauf. Führen Sie den Profiler erneut aus und wiederholen Sie den Arbeitsschritt, sobald dies erledigt ist. Wenn Sie so arbeiten, werden die Gewinne mit jedem Schritt geringer. Ab einem bestimmten Punkt müssen Sie entscheiden, dass die Ergebnisse gut genug sind. Wenn Ihre Bemühungen nur 10% Verbesserung ergeben, dann sind Sie bereits weit über den Punkt hinaus, wo Sie hätten stoppen sollen.

Vergessen Sie nicht das Gesamtbild. Zum Beispiel sollten Sie sich fragen, ob der Code überhaupt unbedingt ausgeführt werden muss, statt seine Geschwindigkeit zu verbessern. Kann er mit anderem Code kombiniert werden? Können bereits ausgeführte Berechnungen gespeichert und später wieder verwertet werden? Eine Optimierung kann man sich auch sparen, wenn der Code dort ausgeführt wird, wo ihn der Benutzer niemals bemerken wird. Noch schlimmer, der Code ist eventuell bereits optimiert und führt kostspielige Berechnungen jetzt aus, um dies später zu vermeiden. Code läuft nicht isoliert und ebenso wenig der Optimierungsprozess.

# Hinweise

## Die Grundlagen

Bilden Sie einen Maßstab nach jeder Code-Änderung aus und führen Sie Buch über alle Änderungen, und wie sie den Maßstab beeinflussen. So können Sie Fehler rückgängig machen und in Zukunft erst gar nicht wiederholen.

Stellen Sie sicher, dass Ihr Code korrekt und fehlerfrei ist, bevor Sie mit der Optimierung beginnen. Überprüfen Sie nach der Optimierung, ob dies dann auch noch zutrifft.

Optimieren Sie zunächst auf hoher Ebene, bevor es an die Details geht.

Verwenden Sie den passenden Algorithmus. Das klassische Beispiel ist, Quick-Sort statt Bubble-Sort zu verwenden. Es gibt viele andere. Einige belegen wenig Arbeitsspeicher, andere brauchen wenig Rechenleistung. Schauen Sie auch, ob Sie Kompromisse eingehen können: Es geht auch schneller als Quick-Sort.

Optimierungen sind ein Abwägen. Ergebnisse zwischenzuspeichern beschleunigt Berechnungen, aber erhöht den Speicherverbrauch. Daten auf einen Datenträger zu speichern spart Arbeitsspeicher, aber kostet Zeit beim Laden vom Datenträger.

Wählen Sie unbedingt eine große Vielfalt an Eingangsgrößen, die Sie optimieren. Wenn Sie dies nicht tun, können Sie schnell mit einem sorgfältig für nur eine einzige Datei optimierten Code enden.

Vermeiden Sie aufwändige Vorgänge: Mehrere kleine Plattenlesevorgänge, Belegung von sehr viel Arbeitsspeicher, bis das Auslagern beginnt. Vermeiden Sie unnötige Schreib- und Lesevorgänge auf Datenträger. Das Netzwerk ist auch langsam. Vermeiden Sie auch grafische Operationen, die eine Antwort vom X-Server erfordern.

## Fallen für die Unvorsichtigen

Nehmen Sie sich vor Nebeneffekten in Acht. Es kann oft seltsame Interaktionen zwischen verschiedenen Code-Bereichen geben. Eine Geschwindigkeitsverbesserung in einem Teil kann einen anderen Teil ausbremsen.

Beim zeitlichen Abstimmen von Code - auch auf unbelasteten Systemen - sorgen externe Ereignisse für Unregelmäßigkeiten bei der Zeitplanung. Bilden Sie Durchschnittswerte von mehreren Durchläufen. Wenn der Code sehr kurz ist, so kann die Zählergenauigkeit zu einem Problem werden. In diesem Fall messen Sie die Zeit 100 oder 1000 Mal, die der Rechner zur Abarbeitung braucht. Wenn Sie Zeitspannen von einigen Sekunden messen, dann sind Sie im grünen Bereich.

Es ist sehr leicht, vom Profiler in die Irre geführt zu werden. Es gibt Geschichten über Leute, die die Untätigkeitsphase perfekt optimiert haben, weil das Programm ja die meiste Zeit in Untätigkeit verbringt! Optimieren Sie keinen Code, von dessen Wirkung der Benutzer ohnehin keine Notiz nimmt.

Vergessen Sie nicht den Ressourcenverbrauch des X-Servers. Der Speicherverbrauch Ihres Programms enthält nicht die Pixmaps, die vom X-Server verarbeitet werden, aber sie verbrauchen dennoch Speicher. Verwenden Sie `xrestop`, um zu sehen, welche Ressourcen Ihr Programm beansprucht.

## Tipps mit niedrigem Niveau

Seien Sie bei der Optimierung des Speicherverbrauchs umsichtig bezüglich des Unterschieds zwischen Spitzen- und durchschnittlicher Speicherbelegung. Einiger Speicher wird quasi immer belegt, das ist nicht gut. Einiger wird nur kurzzeitig belegt, dies ist in Ordnung. Werkzeuge wie »massif« verwenden das Raum-Zeit-Konzept, d.h. das Produkt von belegtem Arbeitsspeicher und der Belegungsdauer.

Stoppen Sie die Zeit vereinfachter Code-Teile, die nur Entscheidendes erledigen. Dies legt eine absolute untere Grenze für die Laufzeit Ihres Codes fest. Zum Beispiel sollten Sie beim Optimieren einer Schleife die leere Schleife stoppen. Wenn dies immer noch zu lang ist, dann wird keine Mikro-Optimierung Abhilfe schaffen und Sie müssen das grundlegende Design ändern. Stellen Sie sicher, dass der Compiler dabei nicht ihre leeren Schleifen rationalisiert.

Gliedern Sie Code aus Schleifen aus. Ein etwas umfangreicheres Stück Code, das nur einmal ausgeführt wird, ist um Einiges schneller als ein simples Stück Code, was dafür tausend Schleifen durchläuft. Vermeiden Sie es, langsamen Code allzu oft aufzurufen.

Geben Sie dem Compiler so viele Hinweise wie möglich. Verwenden Sie das const-Schlüsselwort. Verwenden Sie `G_INLINE_FUNC` für kurze, häufig aufgerufene Funktionen. Schauen Sie nach `G_GNUC_PURE`, `G_LIKELY` und weiteren diversen glib-Makros. Verwenden Sie diese Makros anstelle der gcc-spezifischen Schlüsselwörter, um die Portierbarkeit zu erhalten.

Verwenden Sie keine Assemblersprachen. Sie sind nicht portierbar. Während sie auf dem einen Prozessor unglaublich schnell sind, ist nicht unbedingt garantiert, dass dies auf jedem von der Architektur unterstützten Prozessor genauso ist (beispielsweise Athlon vs Pentium 4).

Schreiben Sie eine vorhandene Bibliotheksroutine nicht neu, es sei denn, Sie sind sich sicher, dass sie unakzeptabel langsam ist. Viele prozessorintensive Bibliotheksroutinen wurden bereits optimiert. Andererseits sind einige Routinen tatsächlich langsam, insbesondere diejenigen, die Systemaufrufe an das Betriebssystem absetzen.

Halten Sie die Zahl der verlinkten Bibliotheken gering. Je weniger Bibliotheken gelinkt werden müssen, umso schneller startet das Programm. Dies ist in GNOME allerdings schwierig.

## Tricks auf hohem Niveau

Nutzen Sie Nebenläufigkeit aus. Das bedeutet nicht nur mehrere Prozessoren zu verwenden, es bedeutet auch die Zeit auszunutzen, in welcher der Anwender über seine weiteren Aktivitäten nachdenkt, und in Vorausschau einige Berechnungen auszuführen. Führen Sie Berechnungen aus, während Daten vom Datenträger geladen werden. Nutzen Sie verschiedene Ressourcen, wenn vorhanden, gleichzeitig.

Bluffen Sie. Der Benutzer sollte denken, dass sein Rechner schnell ist, aber es ist gleich, ob er es wirklich ist oder nicht. Die Zeit zwischen dem Befehl und der darauf folgenden Antwort ist maßgebend. Es ist gleichgültig, ob die Antwort vorausberechnet wird, zwischengespeichert oder in irgendeiner anderen Weise später zu einem günstigeren Zeitpunkt ausgewertet wird, solange der Benutzer genau das bekommt, was er erwartet.

Erledigen Sie Dinge in der Idle-Schleife. Dies ist einfacher zu programmieren als Multi-Threading, aber die Dinge geschehen vom Benutzer unbemerkt. Seien Sie jedoch vorsichtig. Wenn Ihr Programm sehr viel Zeit in der Idle-Schleife verbringt, dann wird es träge. Stellen Sie sicher, dass die Kontrolle regelmäßig an die Hauptschleife übergeben wird.

Falls alles Andere scheitern sollte, informieren Sie den Benutzer mit einem Fortschrittsbalken drüber, dass es derzeit nur langsam voran geht. Das dürfte ihm lieber sein, als wenn Sie nur die Ergebnisse präsentieren würden. Er möchte zumindest wissen, dass das Programm nicht abgestürzt ist und er dann erst einmal eine Tasse Kaffee trinken gehen kann.