Gardez les optimisations locales, rendez-les évidentes, documentez-les bien et simplifiez la comparaison des versions optimisées entre elles et avec la version non optimisée, à la fois en termes de code source et de performances d'exécution.
Réponse complète
Si de telles optimisations sont vraiment importantes pour votre produit, vous devez non seulement savoir pourquoi les optimisations étaient utiles auparavant, mais également fournir suffisamment d'informations pour aider les développeurs à savoir si elles seront utiles à l'avenir.
Idéalement, vous devez inscrire les tests de performances dans votre processus de génération afin de savoir quand les nouvelles technologies invalident les anciennes optimisations.
Rappelles toi:
La première règle d'optimisation de programme: ne le faites pas.
La deuxième règle de l'optimisation des programmes (pour les experts seulement!): Ne le faites pas encore. "
- Michael A. Jackson
Afin de savoir si le moment est venu, il faut des analyses comparatives et des tests.
Comme vous le mentionnez, le plus gros problème avec le code hautement optimisé est qu'il est difficile à maintenir, donc, dans la mesure du possible, vous devez garder les portions optimisées séparées des portions non optimisées. Que vous le fassiez via la liaison au moment de la compilation, les appels de fonctions virtuelles d'exécution ou quelque chose entre les deux, cela n'a pas d'importance. Ce qui devrait être important, c'est que lorsque vous exécutez vos tests, vous souhaitez pouvoir tester toutes les versions qui vous intéressent actuellement.
Je serais enclin à construire un système de telle manière que la version de base non optimisée du code de production puisse toujours être utilisée pour comprendre l' intention du code, puis à construire différents modules optimisés à côté de celui-ci contenant la ou les versions optimisées, documentant explicitement partout la version optimisée diffère de la ligne de base. Lorsque vous exécutez vos tests (unitaires et d'intégration), vous les exécutez sur la version non optimisée et sur tous les modules optimisés actuels.
Exemple
Par exemple, supposons que vous ayez une fonction de transformation de Fourier rapide . Peut-être que vous avez une implémentation algorithmique de base fft.c
et des tests fft_tests.c
.
Vient ensuite le Pentium et vous décidez d'implémenter une version à virgule fixe en fft_mmx.c
utilisant les instructions MMX . Plus tard, le pentium 3 arrive et vous décidez d'ajouter une version qui utilise les extensions Streaming SIMD dans fft_sse.c
.
Maintenant, vous voulez ajouter CUDA , donc vous ajoutez fft_cuda.c
, mais trouvez qu'avec le jeu de données de test que vous utilisez depuis des années, la version CUDA est plus lente que la version SSE! Vous faites une analyse et finissez par ajouter un jeu de données 100 fois plus grand et vous obtenez la vitesse que vous attendez, mais maintenant vous savez que le temps de configuration pour utiliser la version CUDA est important et qu'avec de petits jeux de données, vous devriez utiliser un algorithme sans ce coût d'installation.
Dans chacun de ces cas, vous implémentez le même algorithme, tous doivent se comporter de la même manière, mais s'exécuteront avec des efficacités et des vitesses différentes sur différentes architectures (si elles s'exécutent). Du point de vue du code cependant, vous pouvez comparer n'importe quelle paire de fichiers source pour savoir pourquoi la même interface est implémentée de différentes manières et généralement, le plus simple sera de se référer à la version originale non optimisée.
Il en va de même pour une implémentation OOP où une classe de base qui implémente l'algorithme non optimisé et des classes dérivées implémentent différentes optimisations.
L'important est de garder les mêmes choses qui sont les mêmes , pour que les différences soient évidentes .