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.