Über Premature Optimization

von Hubert Schmid vom 2013-06-30

Premature optimization is the root of all evil. Diese plakative Aussage findet sich in dem 1974 erschienenen Aufsatz Structured Programming with go to Statements von Donald E. Knuth. Damit beschreibt er seine Beobachtung, dass Entwickler viel Zeit mit der Optimierung der Laufzeiteffizienz von Programmteilen verbringen, die nicht auf dem kritischen Pfad liegen, und dass sich diese Optimierungsversuche besonders negativ auf den Aufwand für Fehlersuche und Wartung auswirken.

Zwei typische Beispiele: Komplexe Datenstrukturen, die zwar effizienter, für den konkreten Anwendungsfall allerdings unnötig sind. Sowie Mikro-Optimierungen, die zu Lasten ungewöhnlicher Kontroll- und Datenflüsse ein wenig Performance gewinnen. Einfach formuliert definiert sich also Premature Optimization durch die folgenden beiden Eigenschaften:

Wichtig für die Definition ist, dass beide Eigenschaften erfüllt sind. Denn falls die Optimierung für den Erfolg der Anwendung notwendig ist, handelt es sich nicht um Premature Optimization. Analog gilt das für Maßnahmen, die keine zusätzlichen Aufwände verursachen.

40 Jahre später

Seit der Veröffentlichung von Donald E. Knuth sind fast 40 Jahre vergangen, und Premature Optimization gehört heute gefühlt nicht mehr zu den größten Problemen der Softwareentwicklung. Dazu haben aus meiner Sicht mehrere Dinge beigetragen. Natürlich gehört dazu, dass das Problem überhaupt benannt und diskutiert worden ist. Mindestens genauso wichtig finde ich allerdings die folgenden beiden Punkte:

Ich kenne das aus eigener Erfahrung: Vor 20 Jahren habe ich noch alle Nase lang verkettete Listen und balancierte Bäume implementiert. Heutzutage gibt es keinen Bedarf mehr dafür – nur die Laufzeitkomplexität sollte man natürlich kennen.

Ein neues Problem

In Zusammenhang zur Premature Optimization ist in den letzten zehn Jahren allerdings ein ganz anderes Problem entstanden, das sich entweder durch gefühlt schlechtes Antwortverhalten oder durch schlechte Energieeffizienz manifestiert. Am einfachsten lässt es sich an einem typischen Fall erklären:

Häufig sieht man in Java eine LinkedList an Stelle einer ArrayList, ohne dass es dafür einen ersichtlichen Grund gibt. Dabei ist Erstere in solchen Situationen Letzteren in fast jeder Hinsicht unterlegen. Die falsche Verwendung ist in der Regel auf Unkenntnis zurückzuführen. Bedenklich ist dabei vor allem, dass der Code nicht korrigiert wird. Denn diese Versuche werden häufig als Premature Optimization abgetan. Da die Verwendung passender Datenstrukturen keinen zusätzlichen Aufwand verursachen, ist es aber genau das nicht. Sehr wohl wirkt es sich aber stark negativ auf die Effizienz aus.

Premature Pessimization

Diese Entwicklung ist ein immer größer werdendes Problem. Mittlerweile hat sich dafür der Begriff Premature Pessimization etabliert. Er bezeichnet Code, der die gleiche Komplexität wie idiomatischer Code aufweist, gleichzeitig jedoch deutlich ineffizienter ist – unabhängig vom Nachweis des Nutzens für die gesamte Anwendung. Herb Sutter definiert den Begriff ähnlich: Premature pessimization is when you write code that is slower than it needs to be, usually by asking for unnecessary extra work, when equivalently complex code would be faster and should just naturally flow out of your fingers.

Neben der Wahl der passenden Datenstruktur gibt es weitere typische Beispiele: In C++ ist beispielsweise die Bevorzugung des Pre-Increment-Operators ++i gegenüber dem Post-Increment-Operator i++ idiomatisch – genauso wie die Parameterübergabe per const-Referenz, wenn die Argumente weder kopiert noch verändert werden.

Fazit

Meiner Erfahrung nach gibt es zwei unterschiedliche Arten von Performance-Problemen: Im einen Fall gibt es einen klaren Flaschenhals, wohingegen sich im anderen Fall die Laufzeit gleichmäßig auf viele Stellen verteilt. Ersteres kann vergleichsweise einfach mit klassischem Profiling identifiziert und eingegrenzt werden. Letzteres ist dagegen das Ergebnis üblicherweise das Ergebnis der Premature Pessimization und ist durch konsequentes Refactoring zu beheben. Daher sollte man besser das unnötige Premature Pessimization von Beginn an vermeiden.