J'utilise trop de RAM. Comment cela peut-il être mesuré?

19

Je voudrais savoir combien de RAM j'utilise dans mon projet, pour autant que je sache, il n'y a aucun moyen de résoudre cela (à part le parcourir et le calculer moi-même). Je suis arrivé à un stade dans un projet assez important où j'ai déterminé que je manque de RAM.

J'ai déterminé cela parce que je peux ajouter une section, puis tout l'enfer se déchaîne ailleurs dans mon code sans raison apparente. Si je #ifndefquelque chose d'autre, ça marche à nouveau. Il n'y a rien de mal à programmer avec le nouveau code.

J'ai soupçonné pendant un moment que j'arrivais à la fin de la mémoire RAM disponible. Je ne pense pas que j'utilise trop de pile (bien que ce soit possible), quelle est la meilleure façon de déterminer la quantité de RAM que j'utilise réellement?

En passant par et en essayant de le résoudre, j'ai des problèmes quand j'arrive aux énumérations et aux structures; combien de mémoire coûtent-ils?

première édition: ÉGALEMENT, j'ai tellement édité mon croquis depuis le début, ce ne sont pas les résultats réels que j'ai initialement obtenus, mais c'est ce que j'obtiens maintenant.

  text     data     bss     dec     hex filename
 17554      844     449   18847    499f HA15_20140317w.cpp.elf
 16316      694     409   17419    440b HA15_20140317w.cpp.elf
 17346      790     426   18562    4882 HA15_20140317w.cpp.elf

La première ligne (avec le texte 17554) ne fonctionnait pas, après beaucoup de modifications, la deuxième ligne (avec le texte 16316) fonctionne comme il se doit.

edit: la troisième ligne a tout fonctionne, lecture en série, mes nouvelles fonctions, etc. J'ai essentiellement supprimé quelques variables globales et du code en double. Je mentionne cela parce que (comme suspect), il ne s'agit pas de ce code par sae, il doit être sur l'utilisation de la RAM. Ce qui me ramène à la question d'origine, "comment la mesurer au mieux". Je vérifie toujours quelques réponses, merci.

Comment puis-je réellement interpréter les informations ci-dessus?

Jusqu'à présent, ma compréhension est:

`TEXT` is program instruction memory
`DATA` is variables (unitialised?) in program memory
`BSS`  is variables occupying RAM

puisque BSS est considérablement inférieur à 1024 octets, pourquoi le second fonctionne-t-il, mais pas le premier? Si c'est le cas, les DATA+BSSdeux occupent plus de 1024.

ré-éditer: j'ai édité la question pour inclure le code, mais maintenant je l'ai supprimé car cela n'avait vraiment rien à voir avec le problème (à part peut-être de mauvaises pratiques de codage, des déclarations de variables et autres). Vous pouvez consulter le code en parcourant les modifications si vous voulez vraiment le voir. Je voulais revenir à la question actuelle, qui était plus basée sur: Comment mesurer l'utilisation de la RAM.

Madivad
la source
Je pensais que j'ajouterais, j'ai ajouté plusieurs nouvelles sections de code au cours des dernières semaines, puis l'ai optomisé jusqu'à ce qu'il fonctionne, mais maintenant je n'ai ajouté qu'un demi-octet vars d'octets et j'ai terminé ... :(
Madivad
Utilisez-vous le Stringtype dans vos programmes? Ceci est connu pour effectuer des allocations et des libérations de mémoire dynamique fréquentes, ce qui peut fragmenter le tas au point où vous ne pouvez plus conserver de mémoire.
jfpoilpret
@jfpoilpret Je reste loin de Strings à cause des frais généraux. Je suis heureux de travailler avec des tableaux de caractères, cela dit, je définis presque toujours tous mes tableaux de caractères avec une taille fixe (pour le moment, j'ai un tableau de 1 octet qui n'est pas uniquement parce que je change la longueur du contenu pour différentes recompilations.
Madivad
La publication de votre code ici (ou dans pastebin s'il est trop gros) peut permettre de découvrir les problèmes que vous rencontrez avec la mémoire.
jfpoilpret
@jfpoilpret Je ne peux pas vraiment poster le code, il est énorme et malheureusement très gonflé, réparti sur 16 fichiers. C'était un projet que je permettais de grandir bien au-delà de ce qui était nécessaire (c'est plusieurs projets fusionnés ensemble). Je commence maintenant à le séparer, ce qui aidera certainement à résoudre le problème. Bien qu'il y ait certaines parties que j'ai besoin que les gens regardent (ou me guident), je les posterai plus tard.
Madivad

Réponses:

15

Vous pouvez utiliser les fonctions fournies AVRGCC: Monitoring Stack Usage

La fonction était destinée à vérifier l'utilisation de la pile, mais elle signale la RAM réelle qui n'a jamais été utilisée (pendant l'exécution). Il le fait en "peignant" (remplissant) la RAM avec une valeur connue (0xC5), puis en vérifiant la zone RAM en comptant combien d'octets ont toujours la même valeur initiale.
Le rapport montrera la RAM qui n'a pas été utilisée (RAM libre minimale) et vous pouvez donc calculer la RAM maximale qui a été utilisée (RAM totale - RAM signalée).

Il y a deux fonctions:

  • StackPaint est exécuté automatiquement lors de l'initialisation et "peint" la RAM avec la valeur 0xC5 (peut être modifiée si nécessaire).

  • StackCount peut être appelé à tout moment pour compter la RAM qui n'a pas été utilisée.

Voici un exemple d'utilisation. Ne fait pas grand-chose mais vise à montrer comment utiliser les fonctions.

// -----------------------------------------------------------------------------
extern uint8_t _end;
extern uint8_t __stack;

void StackPaint(void) __attribute__ ((naked)) __attribute__ ((section (".init1")));

void StackPaint(void)
{
#if 0
    uint8_t *p = &_end;

    while(p <= &__stack)
    {
        *p = 0xc5;
        p++;
    }
#else
    __asm volatile ("    ldi r30,lo8(_end)\n"
                    "    ldi r31,hi8(_end)\n"
                    "    ldi r24,lo8(0xc5)\n" /* STACK_CANARY = 0xc5 */
                    "    ldi r25,hi8(__stack)\n"
                    "    rjmp .cmp\n"
                    ".loop:\n"
                    "    st Z+,r24\n"
                    ".cmp:\n"
                    "    cpi r30,lo8(__stack)\n"
                    "    cpc r31,r25\n"
                    "    brlo .loop\n"
                    "    breq .loop"::);
#endif
} 


uint16_t StackCount(void)
{
    const uint8_t *p = &_end;
    uint16_t       c = 0;

    while(*p == 0xc5 && p <= &__stack)
    {
        p++;
        c++;
    }

    return c;
} 

// -----------------------------------------------------------------------------

void setup() {

Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
Serial.println(StackCount(), DEC);  // calls StackCount() to report the unused RAM
delay(1000);
}
alexan_e
la source
morceau de code intéressant, merci. Je l'ai utilisé et cela suggère qu'il y a plus de 600 octets disponibles, mais quand j'enterre cela dans les sous-marins plus profonds, il réduit, mais n'efface pas. Peut-être que mon problème est ailleurs.
Madivad
@Madivad Notez que ces 600+ octets représentent la quantité minimale de RAM disponible jusqu'au moment où vous avez appelé le StackCount. Peu importe la profondeur de l'appel, si la majorité du code et des appels imbriqués ont été exécutés avant d'appeler StackCount, le résultat sera correct. Ainsi, par exemple, vous pouvez laisser votre carte fonctionner pendant un certain temps (aussi longtemps qu'il faut pour obtenir une couverture de code suffisante ou idéalement jusqu'à ce que vous obteniez le comportement incorrect que vous décrivez), puis appuyez sur un bouton pour obtenir la RAM signalée. S'il y en a assez, ce n'est pas la cause du problème.
alexan_e
1
Merci @alexan_e, j'ai créé une zone sur mon écran qui le signale maintenant, alors au fur et à mesure que je progresse au cours des prochains jours, je vais regarder ce numéro avec intérêt, surtout quand il échoue! Merci encore
Madivad
@Madivad Veuillez noter que la fonction donnée ne rapportera pas de résultats corrects si malloc () est utilisé dans le code
alexan_e
merci pour cela, je le sais, cela a été mentionné. Pour autant que je sache, je ne l'utilise pas (je sais qu'il peut y avoir une bibliothèque qui l'utilise, je n'ai pas encore vérifié complètement).
Madivad
10

Les principaux problèmes que vous pouvez rencontrer avec l'utilisation de la mémoire au moment de l'exécution sont les suivants:

  • pas de mémoire disponible dans le tas pour les allocations dynamiques ( mallocou new)
  • pas de place sur la pile lors de l'appel d'une fonction

Les deux sont en fait les mêmes que l'AVR SRAM (2K sur Arduino) est utilisé pour les deux (en plus des données statiques dont la taille ne change jamais pendant l'exécution du programme).

En général, l'allocation dynamique de mémoire est rarement utilisée sur les MCU, seules quelques bibliothèques l'utilisent généralement (l'une d'entre elles est la Stringclasse, que vous avez mentionnée que vous n'utilisez pas, et c'est un bon point).

La pile et le tas peuvent être vus dans l'image ci-dessous (avec la permission d' Adafruit ): entrez la description de l'image ici

Par conséquent, le problème le plus attendu provient du débordement de pile (c'est-à-dire lorsque la pile se développe vers le tas et déborde dessus, puis -si le tas n'a pas été utilisé du tout - déborde sur la zone de données statiques de la SRAM. À ce moment-là, vous avez un risque élevé:

  • la corruption des données (c'est-à-dire le tas surécrit le tas ou les données statiques), vous donnant un comportement incompréhensible
  • la corruption de la pile (c'est-à-dire que le tas ou les données statiques écrasent le contenu de la pile), entraînant généralement un plantage

Afin de connaître la quantité de mémoire qui reste entre le haut du tas et le haut de la pile (en fait, nous pourrions l'appeler le bas si nous représentons à la fois le tas et la pile sur la même image comme illustré ci-dessous), vous peut utiliser la fonction suivante:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Dans le code ci-dessus, __brkvalpointe vers le haut du tas mais c'est 0quand le tas n'a pas été utilisé, auquel cas nous utilisons &__heap_startqui pointe vers __heap_start, la première variable qui marque le bas du tas; &vpointe bien sûr vers le haut de la pile (il s'agit de la dernière variable poussée sur la pile), d'où la formule ci-dessus renvoie la quantité de mémoire disponible pour la pile (ou le tas si vous l'utilisez) pour croître.

Vous pouvez utiliser cette fonction à divers endroits de votre code pour essayer de savoir où cette taille diminue considérablement.

Bien sûr, si jamais vous voyez cette fonction renvoyer un nombre négatif, il est trop tard: vous avez déjà survolé la pile!

jfpoilpret
la source
1
Aux modérateurs: désolé d'avoir mis ce post sur le wiki de la communauté, j'ai dû faire quelque chose de mal en tapant, au milieu du post. Veuillez le remettre ici car cette action n'était pas intentionnelle. Merci.
jfpoilpret
merci pour cette réponse, je n'ai littéralement trouvé que ce morceau de code il y a à peine une heure (au bas du terrain de jeux.arduino.cc/Code/AvailableMemory#.UycOrueSxfg ). Je ne l'ai pas encore inclus (mais je le ferai) car j'ai une assez grande zone de débogage sur mon écran. Je pense que j'ai été confus quant à l'attribution dynamique de choses. Est-ce mallocet newla seule façon de le faire? Si oui, alors je n'ai rien de dynamique. De plus, je viens d'apprendre que l'ONU a 2K de SRAM. Je pensais que c'était 1K. En tenant compte de cela, je ne manque pas de RAM! J'ai besoin de chercher ailleurs.
Madivad
En plus, il y en a aussi calloc. Mais vous utilisez peut-être des bibliothèques tierces qui utilisent l'allocation dynamique à votre insu (vous devez vérifier le code source de toutes vos dépendances pour en être sûr)
jfpoilpret
2
Intéressant. Le seul "problème" est qu'il signale la RAM libre au point où il est appelé, donc à moins qu'il ne soit placé dans la bonne partie, vous ne remarquerez peut-être pas un dépassement de pile. La fonction que j'ai fournie semble avoir un avantage dans ce domaine car elle rapporte le minimum de RAM libre jusqu'à ce point, une fois qu'une adresse RAM a été utilisée, elle n'est plus signalée comme étant libre (à la baisse il peut y avoir de la RAM occupée octets correspondant à la valeur "paint" et signalés comme libres). En dehors de cela, une façon convient peut-être mieux que l'autre, selon ce que veut un utilisateur.
alexan_e
Bon point! Je n'avais pas remarqué ce point spécifique dans votre réponse (et pour moi cela ressemblait à un bug en fait), maintenant je vois l'intérêt de "peindre" la zone libre dès le départ. Peut-être pourriez-vous rendre ce point plus explicite dans votre réponse?
jfpoilpret
7

Lorsque vous découvrez comment localiser le fichier .elf généré dans votre répertoire temporaire, vous pouvez exécuter la commande ci-dessous pour vider une utilisation SRAM, où project.elfdoit être remplacé par le .elffichier généré . L'avantage de cette sortie est la possibilité d'inspecter la façon dont votre SRAM est utilisée. Toutes les variables doivent-elles être globales, sont-elles vraiment toutes requises?

avr-objdump -S -j .bss project.elf

project.elf:     file format elf32-avr


Disassembly of section .bss:

00800060 <__bss_start>:
        ...

00800070 <measurementReady>:
        ...

00800071 <cycles>:
        ...

00800073 <measurement>:
  800073:       00 00 00 00                                         ....

00800077 <measurementStart>:
  800077:       00 00 00 00                                         ....

0080007b <timerOverflows>:
  80007b:       00 00 00 00

Notez que cela ne montre pas l'utilisation de la pile ou de la mémoire dynamique comme Ignacio Vazquez-Abrams l'a noté dans les commentaires ci-dessous.

De plus, un avr-objdump -S -j .data project.elfpeut être vérifié, mais aucun de mes programmes ne produit quoi que ce soit avec cela, donc je ne peux pas dire avec certitude s'il est utile. Il était censé répertorier les «données initialisées (non nulles)».

jippie
la source
Ou vous pouvez simplement utiliser avr-size. Mais cela ne vous montrera pas les allocations dynamiques ou l'utilisation de la pile.
Ignacio Vazquez-Abrams
@ IgnacioVazquez-Abrams sur la dynamique, idem pour ma solution. Modifié ma réponse.
jippie
Ok, c'est la réponse la plus intéressante à ce jour. Je l' ai expérimenté avec avr-objdumpet avr-sizeje vais modifier mon post ci - dessus sous peu. Merci pour cela.
Madivad
3

J'ai soupçonné pendant un moment que j'arrivais à la fin de la mémoire RAM disponible. Je ne pense pas que j'utilise trop de pile (bien que ce soit possible), quelle est la meilleure façon de déterminer la quantité de RAM que j'utilise réellement?

Il serait préférable d'utiliser une combinaison d'estimation manuelle et en utilisant l' sizeofopérateur. Si toutes vos déclarations sont statiques, cela devrait vous donner une image précise.

Si vous utilisez des allocations dynamiques, vous pouvez rencontrer un problème une fois que vous commencez à désallouer la mémoire. Cela est dû à la fragmentation de la mémoire sur le tas.

En passant par et en essayant de le résoudre, j'ai des problèmes quand j'arrive aux énumérations et aux structures; combien de mémoire coûtent-ils?

Un enum prend autant d'espace qu'un int. Donc, si vous avez un ensemble de 10 éléments dans une enumdéclaration, ce serait 10*sizeof(int). De plus, chaque variable qui utilise une énumération est simplement un int.

Pour les structures, il serait plus facile à utiliser sizeofpour le savoir. Les structures occupent un espace (minimum) égal à la somme de ses membres. Si le compilateur effectue l'alignement de la structure, cela peut être plus, mais cela est peu probable dans le cas de avr-gcc.

asheeshr
la source
J'assigne statiquement tout ce que je peux. Je n'ai jamais pensé à utiliser sizeofà cet effet. À l'heure actuelle, j'ai près de 400 octets déjà comptabilisés (globalement). Maintenant, je vais parcourir et calculer les énumérations (manuellement) et les structures (dont j'ai quelques-unes et je vais utiliser sizeof), et faire un rapport.
Madivad
Pas sûr que vous ayez vraiment besoin sizeofde connaître la taille de vos données statiques car elles sont imprimées par avrdude IIRC.
jfpoilpret
@jfpoilpret Cela dépend de la version, je pense. Toutes les versions et plates-formes ne fournissent pas cela. Le mien (Linux, plusieurs versions) n'affiche pas l'utilisation de la mémoire pour un, contrairement aux versions Mac.
asheeshr
J'ai recherché la sortie verbeuse, je pensais qu'elle devrait être là, ce n'est pas le cas
Madivad
@AsheeshR Je n'étais pas au courant de cela, le mien fonctionne très bien sur Windows.
jfpoilpret
1

Il existe un programme appelé Arduino Builder qui fournit une visualisation nette de la quantité de flash, SRAM et EEPROM que votre programme utilise.

Arduino Builder

Le constructeur Arduino fait partie de la solution IDE Arduino CodeBlocks . Il peut être utilisé en tant que programme autonome ou via l'IDE Arduino CodeBlocks.

Malheureusement, Arduino Builder est un peu ancien, mais il devrait fonctionner pour la plupart des programmes et la plupart des Arduinos, comme Uno.

sa_leinad
la source