J'ai travaillé sur le développement d'une fonctionnalité sur un de nos produits particuliers. Il y a eu une demande de portage de la même fonctionnalité vers un autre produit. Ce produit est basé sur un microcontrôleur M16C, qui a traditionnellement 64K Flash et 2k de RAM.
C'est un produit mature, et il ne lui reste donc que 132 octets de Flash et 2 octets de RAM.
Pour porter la fonctionnalité demandée (la fonctionnalité elle-même a été optimisée), j'ai besoin de 1400 octets de Flash et ~ 200 octets de RAM.
Quelqu'un at-il des suggestions sur la façon de récupérer ces octets par compactage de code? Quelles sont les choses spécifiques que je recherche lorsque j'essaie de compacter du code de travail déjà existant?
Toutes les idées seront vraiment appréciées.
Merci.
Réponses:
Vous avez deux options: la première consiste à rechercher du code redondant et à le déplacer vers un seul appel pour éliminer la duplication; la seconde consiste à supprimer la fonctionnalité.
Examinez bien votre fichier .map et voyez s'il existe des fonctions dont vous pouvez vous débarrasser ou réécrire. Assurez-vous également que les appels de bibliothèque utilisés sont vraiment nécessaires.
Certaines choses comme la division et les multiplications peuvent apporter beaucoup de code, mais l'utilisation des décalages et une meilleure utilisation des constantes peuvent rendre le code plus petit. Jetez également un oeil à des choses comme les constantes de chaîne et
printf
s. Par exemple chacunprintf
mangera votre rom, mais vous pourrez peut-être avoir quelques chaînes de format partagé au lieu de répéter cette chaîne à plusieurs reprises.Pour la mémoire, voyez si vous pouvez vous débarrasser des globaux et utiliser des autos dans une fonction à la place. Évitez également autant de variables que possible dans la fonction principale, car elles consomment de la mémoire comme le font les globaux.
la source
Il vaut toujours la peine de consulter la sortie du fichier (assembleur) pour rechercher les choses sur lesquelles votre compilateur particulier est particulièrement mauvais.
Par exemple, vous pouvez constater que les variables locales sont très chères, et si l'application est suffisamment simple pour en valoir le risque, le déplacement de quelques compteurs de boucles dans des variables statiques peut économiser beaucoup de code.
Ou l'indexation de tableaux peut être très coûteuse mais les opérations de pointeur beaucoup moins chères. Ou vice versa.
Mais regarder le langage d'assemblage est la première étape.
la source
Les optimisations du compilateur, par exemple,
-Os
dans GCC donnent le meilleur équilibre entre la vitesse et la taille du code. À éviter-O3
, car cela peut augmenter la taille du code.la source
Pour la RAM, vérifiez la plage de toutes vos variables - utilisez-vous des ints où vous pourriez utiliser un caractère? Les tampons sont-ils plus grands que nécessaire?
La compression de code est très dépendante de l'application et du style de codage. Vos montants restants suggèrent que le code a peut-être déjà disparu malgré une compression, ce qui peut signifier qu'il reste peu de choses à avoir.
Jetez également un coup d'œil à la fonctionnalité globale - y a-t-il quelque chose qui n'est pas vraiment utilisé et qui peut être abandonné?
la source
Si c'est un vieux projet mais que le compilateur a été développé depuis, il se peut qu'un compilateur plus récent puisse produire un code plus petit
la source
Il vaut toujours la peine de consulter le manuel de votre compilateur pour les options permettant d'optimiser l'espace.
Pour gcc
-ffunction-sections
et-fdata-sections
avec le--gc-sections
drapeau de éditeur de liens sont bons pour supprimer le code mort.Voici d'autres excellents conseils (axés sur l'AVR)
la source
Vous pouvez examiner la quantité d'espace de pile et d'espace de tas alloués. Vous pourrez peut-être récupérer une quantité importante de RAM si l'une ou l'autre ou les deux sont surallouées.
Je suppose pour un projet qui entre dans 2k de RAM pour commencer il n'y a pas d' allocation de mémoire dynamique (utilisation
malloc
,calloc
etc.). Si tel est le cas, vous pouvez vous débarrasser complètement de votre tas en supposant que l'auteur d'origine a laissé de la RAM allouée pour le tas.Vous devez être très prudent en réduisant la taille de la pile car cela peut provoquer des bogues très difficiles à trouver. Il peut être utile de commencer par initialiser tout l'espace de pile à une valeur connue (autre chose que 0x00 ou 0xff car ces valeurs se produisent déjà couramment), puis d'exécuter le système pendant un certain temps pour voir combien d'espace de pile n'est pas utilisé.
la source
Votre code utilise-t-il des mathématiques à virgule flottante? Vous pourrez peut-être réimplémenter vos algorithmes en utilisant uniquement les mathématiques entières et éliminer les frais généraux liés à l'utilisation de la bibliothèque de virgule flottante C. Par exemple, dans certaines applications, des fonctions telles que sinus, log, exp peuvent être remplacées par des approximations polynomiales entières.
Votre code utilise-t-il de grandes tables de recherche pour des algorithmes tels que les calculs CRC? Vous pouvez essayer de remplacer une version différente de l'algorithme qui calcule les valeurs à la volée, au lieu d'utiliser les tables de recherche. La mise en garde est que le plus petit algorithme est probablement plus lent, alors assurez-vous d'avoir suffisamment de cycles CPU.
Votre code contient-il de grandes quantités de données constantes, telles que des tableaux de chaînes, des pages HTML ou des graphiques en pixels (icônes)? S'il est suffisamment volumineux (disons 10 Ko), il pourrait être utile de mettre en œuvre un schéma de compression très simple pour réduire les données et les décompresser à la volée si nécessaire.
la source
Vous pouvez essayer de réorganiser pour coder beaucoup, dans un style plus compact. Cela dépend beaucoup de ce que fait le code. La clé est de trouver des choses similaires et de les réimplémenter les unes par rapport aux autres. Un extrême serait d'utiliser un langage de niveau supérieur, comme Forth, avec lequel il peut être plus facile d'atteindre une densité de code plus élevée qu'en C ou en assembleur.
Voici Forth pour M16C .
la source
Définissez le niveau d'optimisation du compilateur. De nombreux IDE ont des paramètres qui permettent des optimisations de la taille du code au détriment du temps de compilation (ou peut-être même du temps de traitement dans certains cas). Ils peuvent effectuer le compactage de code en réexécutant leur optimiseur plusieurs fois, en recherchant des modèles optimisables moins courants et une multitude d'autres astuces qui peuvent ne pas être nécessaires pour la compilation occasionnelle / de débogage. Habituellement, par défaut, les compilateurs sont définis sur un niveau moyen d'optimisation. Creusez dans les paramètres et vous devriez pouvoir trouver une échelle d'optimisation basée sur des nombres entiers.
la source
Si vous utilisez déjà un compilateur de niveau professionnel comme IAR, je pense que vous aurez du mal à faire de sérieuses économies en modifiant légèrement le code de bas niveau - vous devrez chercher davantage à supprimer des fonctionnalités ou à effectuer des tâches majeures. réécrit les pièces de manière plus efficace. Vous devrez être un codeur plus intelligent que celui qui a écrit la version originale ... En ce qui concerne la RAM, vous devez examiner très attentivement comment elle est actuellement utilisée et voir s'il est possible de superposer l'utilisation de la même RAM pour différentes choses à différents moments (les syndicats sont utiles pour cela). Les tailles de tas et de piles par défaut d'IAR dans celles ARM / AVR que j'ai eu tendance à être trop généreuses, donc ce serait la première chose à regarder.
la source
Autre chose à vérifier - certains compilateurs sur certaines architectures copient les constantes dans la RAM - généralement utilisés lorsque l'accès aux constantes flash est lent / difficile (par exemple AVR). Par exemple, le compilateur AVR d'IAR nécessite un qualificatif _ _flash pour ne pas copier une constante dans la RAM)
la source
Si votre processeur ne prend pas en charge le matériel pour une pile de paramètres / locale mais que le compilateur essaie de toute façon d'implémenter une pile de paramètres d'exécution, et si votre code n'a pas besoin d'être rentré, vous pourrez peut-être enregistrer le code l'espace en allouant statiquement des variables automatiques. Dans certains cas, cela doit être fait manuellement; dans d'autres cas, les directives du compilateur peuvent le faire. Une allocation manuelle efficace nécessitera le partage de variables entre les routines. Un tel partage doit être fait avec soin, pour garantir qu'aucune routine n'utilise une variable qu'une autre routine considère comme "dans la portée", mais dans certains cas, les avantages de la taille du code peuvent être importants.
Certains processeurs ont des conventions d'appel qui peuvent rendre certains styles de passage de paramètres plus efficaces que d'autres. Par exemple, sur les contrôleurs PIC18, si une routine prend un seul paramètre d'un octet, il peut être passé dans un registre; si cela prend plus que cela, tous les paramètres doivent être passés en RAM. Si une routine prend deux paramètres d'un octet, il peut être plus efficace de "passer" l'un dans une variable globale, puis de passer l'autre comme paramètre. Avec des routines largement utilisées, les économies peuvent s'additionner. Ils peuvent être particulièrement importants si le paramètre transmis via global est un indicateur à bit unique, ou s'il aura généralement une valeur de 0 ou 255 (car des instructions spéciales existent pour stocker un 0 ou 255 dans la RAM).
Sur l'ARM, le fait de mettre des variables globales fréquemment utilisées ensemble dans une structure peut réduire considérablement la taille du code et améliorer les performances. Si A, B, C, D et E sont des variables globales distinctes, le code qui les utilise toutes doit charger l'adresse de chacune dans un registre; s'il n'y a pas suffisamment de registres, il peut être nécessaire de recharger ces adresses plusieurs fois. En revanche, s'ils font partie de la même structure globale MyStuff, le code qui utilise MyStuff.A, MyStuff.B, etc. peut simplement charger une fois l'adresse de MyStuff. Grande victoire.
la source
1.Si votre code dépend de nombreuses structures, assurez-vous que les membres de la structure sont classés de ceux qui occupent le plus de mémoire au moins.
Ex: "uint32_t uint16_t uint8_t" au lieu de "uint16_t uint8_t uint32_t"
Cela garantira un rembourrage de structure minimum.
2.Utilisez const pour les variables, le cas échéant. Cela garantira que ces variables seront en ROM et ne consommeront pas de RAM
la source
Quelques astuces (peut-être évidentes) que j'ai utilisées avec succès pour compresser le code d'un client:
Drapeaux compacts en champs de bits ou masques de bits. Cela peut être bénéfique, car les booléens sont généralement stockés sous forme d'entiers, ce qui gaspille de la mémoire. Cela permettra d'économiser à la fois de la RAM et de la ROM et n'est généralement pas effectué par le compilateur.
Recherchez la redondance dans le code et utilisez des boucles ou des fonctions pour exécuter des instructions répétées.
J'ai également économisé de la ROM en remplaçant de nombreuses
if(x==enum_entry) <assignment>
instructions de constantes par un tableau indexé, en prenant soin que les entrées enum puissent être utilisées comme index de tableaula source
Si vous le pouvez, utilisez des fonctions en ligne ou des macros de compilation au lieu de petites fonctions. Il y a des frais généraux de taille et de vitesse avec des arguments qui passent et qui peuvent être corrigés en rendant la fonction en ligne.
la source
int get_a(struct x) {return x.a;}
Modifiez les variables locales pour qu'elles aient la même taille que vos registres CPU.
Si le processeur est de 32 bits, utilisez des variables de 32 bits même si la valeur maximale ne dépassera jamais 255. Si vous avez utilisé une variable de 8 bits, le compilateur ajoutera du code pour masquer les 24 bits supérieurs.
La première place que je regarderais est celle des variables for-loop.
Cela peut sembler un bon endroit pour une variable 8 bits, mais une variable 32 bits peut produire moins de code.
la source