42 votes

Optimiser l'espace plutôt que la vitesse en C++

Lorsque l'on parle d'optimisation, les gens ont tendance à penser à la vitesse. Mais qu'en est-il des systèmes embarqués où la vitesse n'est pas si critique, mais où la mémoire est une contrainte majeure ? Quelles sont les lignes directrices, les techniques et les astuces qui peuvent être utilisées pour gagner ces kilo-octets supplémentaires dans la ROM et la RAM ? Comment peut-on "profiler" un code pour voir où se trouve le gonflement de la mémoire ?

P.S. On pourrait dire que l'optimisation "prématurée" de l'espace dans les systèmes embarqués n'est pas si mauvaise que cela, car elle laisse plus de place au stockage des données et à l'évolution des fonctionnalités. Cela vous permet également de réduire les coûts de production du matériel parce que votre code peut fonctionner sur une ROM/RAM plus petite.

P.P.S. Les références à des articles et à des livres sont également les bienvenues !

P.P.P.S. Ces questions sont étroitement liées : 404615 , 1561629

30voto

Tim Lovell-Smith Points 2635

Mon expérience d'un extrêmement dans un environnement de mémoire embarquée contraint :

  • Utiliser des tampons de taille fixe. N'utilisez pas de pointeurs ou d'allocation dynamique, car ils entraînent trop de frais généraux.
  • Utilisez le plus petit type de données int qui fonctionne.
  • N'utilisez jamais la récursivité. Utilisez toujours le bouclage.
  • Ne pas passer beaucoup de paramètres de fonction. Utilisez plutôt des globaux :)

13voto

James Points 11054

Il y a beaucoup de choses que vous pouvez faire pour réduire vos empreintes de mémoire, je suis sûr que des gens ont écrit des livres sur le sujet, mais quelques-unes des principales sont les suivantes :

  • Options du compilateur pour réduire la taille du code (y compris -Os et les options d'empaquetage/alignement)

  • Options de l'éditeur de liens pour supprimer le code mort

  • Si vous chargez à partir de la flash (ou de la ROM) vers la mémoire vive pour l'exécuter (plutôt que d'exécuter à partir de la flash), utilisez une image flash compressée et décompressez-la à l'aide de votre chargeur de démarrage.

  • Utiliser l'allocation statique : un tas est un moyen inefficace d'allouer une mémoire limitée, et s'il est contraint, il risque d'échouer en raison de la fragmentation.

  • Outils permettant de trouver le point culminant de la pile (en général, ils remplissent la pile d'un motif, exécutent le programme, puis voient où le motif reste), afin que vous puissiez définir la taille de la pile de manière optimale.

  • Et bien sûr, l'optimisation des algorithmes que vous utilisez pour l'empreinte mémoire (souvent au détriment de la vitesse).

12voto

Emile Cormier Points 13654

Quelques exemples évidents

  • Si la vitesse n'est pas critique, exécutez le code directement à partir de Flash.
  • Déclarer des tableaux de données constants en utilisant const . Cela évitera que les données soient copiées de la mémoire flash à la mémoire vive.
  • Les tableaux de données volumineux doivent être compactés en utilisant les types de données les plus petits et dans l'ordre correct pour éviter le remplissage.
  • Utiliser la compression pour les grands ensembles de données (tant que le code de compression ne l'emporte pas sur les données).
  • Désactiver la gestion des exceptions et le RTTI.
  • Quelqu'un a-t-il mentionné l'utilisation de -Os ? ;-)

Transformer les connaissances en données

L'une des règles de la Philosophie Unix peut contribuer à rendre le code plus compact :

Règle de représentation : Pliez les connaissances dans les données afin que la logique du programme puisse être stupide et robuste.

Je ne compte plus le nombre de fois où j'ai vu une logique élaborée, s'étendant sur plusieurs pages, qui aurait pu être pliée en un joli tableau compact de règles, de constantes et de pointeurs de fonctions. Les machines à états peuvent souvent être représentées de cette manière (State Pattern). Le modèle de commande s'applique également. Il s'agit de styles de programmation déclaratifs ou impératifs.

Codes logiques + données binaires au lieu de texte

Au lieu d'enregistrer du texte brut, enregistrez des codes d'événements et des données binaires. Utilisez ensuite un "livre de phrases" pour reconstituer les messages d'événements. Les messages du recueil de phrases peuvent même contenir des spécificateurs de format de type printf, de sorte que les valeurs des données d'événement soient affichées proprement dans le texte.

Minimiser le nombre de fils

Chaque thread a besoin de son propre bloc de mémoire pour la pile et le TSS. Lorsque vous n'avez pas besoin de préemption, envisagez de faire en sorte que vos tâches s'exécutent de manière coopérative au sein d'un même thread ( multi-tâches coopératives ).

Utiliser des pools de mémoire au lieu de les accumuler

Pour éviter la fragmentation du tas, j'ai souvent vu des modules séparés accumuler de grands tampons de mémoire statique pour leur propre usage, même si la mémoire n'est requise qu'occasionnellement. Un pool de mémoire pourrait être utilisé à la place afin que la mémoire ne soit utilisée que "sur demande". Toutefois, cette approche peut nécessiter une analyse et une instrumentation minutieuses pour s'assurer que les pools ne sont pas épuisés au moment de l'exécution.

Allocation dynamique uniquement lors de l'initialisation

Dans les systèmes embarqués où une seule application fonctionne indéfiniment, il est possible d'utiliser l'allocation dynamique d'une manière judicieuse qui n'entraîne pas de fragmentation : Il suffit d'allouer dynamiquement une fois dans vos différentes routines d'initialisation et de ne jamais libérer la mémoire. reserve() vos conteneurs à la bonne capacité et ne les laissez pas croître automatiquement. Si vous devez fréquemment allouer/libérer des tampons de données (par exemple, pour les paquets de communication), utilisez des pools de mémoire. Une fois, j'ai même étendu les temps d'exécution C/C++ pour qu'ils interrompent mon programme si quelque chose essayait d'allouer dynamiquement de la mémoire après la séquence d'initialisation.

7voto

Thomas Matthews Points 19838

Générer un fichier de carte à partir de votre éditeur de liens. Il montrera comment la mémoire est allouée. C'est un bon point de départ pour optimiser l'utilisation de la mémoire. Il montre également toutes les fonctions et la façon dont l'espace de code est organisé.

7voto

Chip Uni Points 4739

Comme pour toute optimisation, il faut d'abord optimiser les algorithmes, puis le code et les données, et enfin le compilateur.

Je ne sais pas ce que fait votre programme, je ne peux donc pas vous donner de conseils sur les algorithmes. Beaucoup d'autres ont écrit sur le compilateur. Voici donc quelques conseils sur le code et les données :

  • Éliminez les redondances dans votre code. Tout code répétitif de trois lignes ou plus, répété trois fois dans votre code, doit être remplacé par un appel de fonction.
  • Éliminez la redondance de vos données. Trouvez la représentation la plus compacte : fusionnez les données en lecture seule et envisagez d'utiliser des codes de compression.
  • Exécutez le code à l'aide d'un profileur ordinaire ; éliminez tout le code qui n'est pas utilisé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X