Halten Sie Optimierungen lokal, machen Sie sie offensichtlich, dokumentieren Sie sie gut und erleichtern Sie den Vergleich der optimierten Versionen untereinander und mit der nicht optimierten Version, sowohl hinsichtlich des Quellcodes als auch der Laufzeitleistung.
Vollständige Antwort
Wenn solche Optimierungen für Ihr Produkt wirklich so wichtig sind , müssen Sie nicht nur wissen, warum die Optimierungen zuvor nützlich waren, sondern auch genügend Informationen bereitstellen, damit Entwickler wissen, ob sie in Zukunft nützlich sein werden.
Im Idealfall müssen Sie Leistungstests in Ihrem Erstellungsprozess verankern, damit Sie herausfinden, wann neue Technologien alte Optimierungen ungültig machen.
Merken:
Die erste Regel der Programmoptimierung: Tun Sie es nicht.
Die zweite Regel der Programmoptimierung (nur für Experten!): Tun Sie es noch nicht. "
- Michael A. Jackson
Um zu wissen, ob jetzt die Zeit gekommen ist, müssen Benchmarking und Tests durchgeführt werden.
Wie Sie bereits erwähnt haben, besteht das größte Problem bei hochoptimiertem Code darin, dass die Wartung schwierig ist. Daher müssen Sie die optimierten Teile so weit wie möglich von den nicht optimierten Teilen trennen. Ob Sie dies durch Verknüpfung zur Kompilierungszeit, virtuelle Laufzeitfunktionsaufrufe oder etwas dazwischen tun, sollte keine Rolle spielen. Was wichtig sein sollte, ist, dass Sie beim Ausführen Ihrer Tests in der Lage sein möchten, alle Versionen zu testen, an denen Sie derzeit interessiert sind.
Ich würde gerne ein System so erstellen, dass die nicht optimierte Basisversion des Produktionscodes immer verwendet werden kann, um die Absicht des Codes zu verstehen , und dann verschiedene optimierte Module daneben erstellen, die die optimierte Version oder die optimierten Versionen enthalten und überall explizit dokumentieren Die optimierte Version unterscheidet sich von der Basislinie. Wenn Sie Ihre Tests (Einheit und Integration) ausführen, führen Sie sie auf der nicht optimierten Version und auf allen aktuell optimierten Modulen aus.
Beispiel
Nehmen wir zum Beispiel an, Sie haben eine Fast Fourier Transform- Funktion. Vielleicht haben Sie eine grundlegende, algorithmische Implementierung in fft.c
und Tests in fft_tests.c
.
Dann kommt der Pentium und Sie entscheiden sich, die Festkomma-Version fft_mmx.c
mithilfe von MMX-Anweisungen zu implementieren . Später kommt das Pentium 3 und Sie beschließen, eine Version hinzuzufügen, die Streaming SIMD Extensions in verwendet fft_sse.c
.
Jetzt möchten Sie CUDA hinzufügen , also fügen Sie hinzu fft_cuda.c
, stellen jedoch fest, dass mit dem Testdatensatz, den Sie seit Jahren verwenden, die CUDA-Version langsamer ist als die SSE-Version! Sie führen einige Analysen durch und fügen am Ende einen Datensatz hinzu, der 100-mal größer ist, und Sie erhalten die erwartete Beschleunigung. Jetzt wissen Sie jedoch, dass die Einrichtungszeit für die Verwendung der CUDA-Version erheblich ist und dass Sie bei kleinen Datensätzen einen verwenden sollten Algorithmus ohne diese Einrichtungskosten.
In jedem dieser Fälle, in denen Sie denselben Algorithmus implementieren, sollten sich alle gleich verhalten, jedoch auf verschiedenen Architekturen mit unterschiedlicher Effizienz und Geschwindigkeit ausgeführt werden (sofern sie überhaupt ausgeführt werden). Unter dem Gesichtspunkt des Codes können Sie jedoch jedes Paar von Quelldateien vergleichen, um herauszufinden, warum dieselbe Schnittstelle auf unterschiedliche Weise implementiert ist. In der Regel ist es am einfachsten, auf die ursprüngliche, nicht optimierte Version zurückzugreifen.
Gleiches gilt für eine OOP-Implementierung, bei der eine Basisklasse, die den nicht optimierten Algorithmus implementiert, und abgeleitete Klassen unterschiedliche Optimierungen implementieren.
Das Wichtigste ist, die gleichen Dinge zu behalten, die gleich sind , damit die Unterschiede offensichtlich sind .