Go Down

Topic: Free Memory : pas le même résultat (Read 10079 times) previous topic - next topic

OLIVIERC67

Bonjour

Ci dessous 2 méthodes pour connaitre la quantité de sram dispo et pas le même résultat. Excusez moi pour la présentation, mais je teste différentes méthodes et c'est brouillon pour le moment.

Le sketch avec FreeRam qui tourne en même temps que MemoryFree (http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1213583720/15)

Soit il y a un bug dans le code, soit (je demande confirmation), le code est chargée de façon dynamique pendant son exécution et ce qui est pas nécessaire est déchargé puis rechargé au prochain loop(). Et donc FreeRam consomme plus que FreeMemory (une dizaine d'octets environ sur cet exemple).

Y a t il une méthode fiable savoir combien il en reste, parce qu'avec quelques Ko de sram c'est pas le moment de gaspiller ?

Quote


#include "MemoryFree.h"


/** Return the number of bytes currently free in RAM. */
static int FreeRam(void) {
  extern int  __bss_end;
  extern int* __brkval;
  int free_memory;
  if (reinterpret_cast<int>(__brkval) == 0) {
    // if no heap use from end of bss section
    free_memory = reinterpret_cast<int>(&free_memory)
                  - reinterpret_cast<int>(&__bss_end);
  } else {
    // use from top of stack to heap
    free_memory = reinterpret_cast<int>(&free_memory)
                  - reinterpret_cast<int>(__brkval);
  }
  return free_memory;
}



void setup()           // run once, when the sketch starts
{
  Serial.begin(9600);
 

}

void loop()            // run over and over again
{
  Serial.print("freeMemory() reports ");
  Serial.println( freeMemory() );
  Serial.print("freeRam() reports ");
  Serial.println( FreeRam() );
 
}



MemoryFree.cpp

Quote

//
#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

extern unsigned int __heap_start;
extern void *__brkval;

/*
* The free list structure as maintained by the
* avr-libc memory allocation routines.
*/
struct __freelist {
  size_t sz;
  struct __freelist *nx;
};

/* The head of the free list structure */
extern struct __freelist *__flp;

#include "MemoryFree.h";

/* Calculates the size of the free list */
int freeListSize() {
  struct __freelist* current;
  int total = 0;

  for (current = __flp; current; current = current->nx) {
    total += 2; /* Add two bytes for the memory block's header  */
    total += (int) current->sz;
  }

  return total;
}

int freeMemory() {
  int free_memory;

  if ((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__heap_start);
  } else {
    free_memory = ((int)&free_memory) - ((int)__brkval);
    free_memory += freeListSize();
  }
  return free_memory;
}


et MemoryFree.h

Quote

// MemoryFree library based on code posted here:
// http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1213583720/15
//
// Extended by Matthew Murdoch to include walking of the free list.

#ifndef   MEMORY_FREE_H
#define MEMORY_FREE_H

#ifdef __cplusplus
extern "C" {
#endif

int freeMemory();

#ifdef  __cplusplus
}
#endif

#endif


fdufnews

Lorsque tu appelles FreeRam ou freeMemory il y a création de variables locales par la fonction et donc tu ne peux pas trouver exactement la même valeur d'une fonction à l'autre.

OLIVIERC67


Lorsque tu appelles FreeRam ou freeMemory il y a création de variables locales par la fonction et donc tu ne peux pas trouver exactement la même valeur d'une fonction à l'autre.


Très clair, merci.

Super_Cinci

Je ne suis pas sûr qu'il y ait allocation dynamique sur les variables locales (ce serait un grosse erreur sur un µP aussi lent). Pour moi, les variables locales sont allouées dans la ram à la compilation, donc ce n'est pas à ce niveau que ça coince.

Je pense plutôt que c'est au niveau de la méthode de calcul, il faudrait regarder dans les codes, et à quoi se réfèrent les variables / constantes.

De plus, comme dans freeRam(), la bonne méthode est de compter la diff entre la fin des variables et le début de la pile. Mais la pile est variable et dépend des différentes fonctions en cours et de leurs paramètres.

peux-tu nous donner les différentes valeurs renvoyées? car si la différence n'est que de 5 octets, c'est peut-être négligeable?

skywodd

Bonjour,

La différence est de combien d'octets ?

Tu as plusieurs facteurs qui peuvent entrer en ligne de compte :
- l'allocation de variables pour l'exécution de la fonction elle même,
- le nombre d'adresses de retour de fonctions dans la pile d'appel à un instant t

Tes deux codes se base sur le nombre d'octets restant dans la ram calculé en fonction de ce que les fonctions de l'avr-libc a alloué / libéré.
Donc les deux fonctions devrait donner un résultat trés proche.
Des news, des tutos et plein de bonnes choses sur http://skyduino.wordpress.com !

barbudor

@Super_Cinci

En C, les variables locales des fonctions sont forcement allouées "dynamiquement", sinon le code ne serait pas récursif.

L'espace mémoire DATA est généralement divisé en 3 portions :
- la bss qui contient toutes les variables globales, allouées statiquement au moment de l'édition de lien.
- la pile (stack) qui contient les :
   - les paramètres des fonctions
   - l'adresse retour
   - les variables locales
  La pile est une zone de mémoire qui se remplit et se vide de manière linéaire et continue lorsque on appelle des fonctions ou retourne d'une fonction.
- le tas (heap) qui est destiné aux allocations dynamique dans lequel on peut allouer (malloc/calloc/new) et libérer (free/delete) des blocs de mémoire. Le tas devient généralement fragmenté au bout d'un moment ce qui peut le rendre inutilisable. L'utilisation de mémoire dynamique doit être réduit au minimum, si possible uniquement durant les phases d'initialisations quand on peut encore dire 'Help, pas assez de mémoire".
par exemple la classe String est énormément basé sur l'allocation dynamique. Utiliser String conduit à fragmenter sa mémoire très rapidement.

Il est clair que FreeRam() ne fonctionne pas de la même façon que  freeMemory()
FreeRam() fait un simple calcul alors que freeMemory() parcours le tas pour accumuler la taille des blocs libre.

Donc je pense que freeMemory() doit donner une valeur significativement plus faible mais plus juste que freeRam().

A creuser...

OLIVIERC67


@Super_Cinci

En C, les variables locales des fonctions sont forcement allouées "dynamiquement", sinon le code ne serait pas récursif.
...
- le tas (heap) qui est destiné aux allocations dynamique dans lequel on peut allouer (malloc/calloc/new) et libérer (free/delete) des blocs de mémoire. Le tas devient généralement fragmenté au bout d'un moment ce qui peut le rendre inutilisable. L'utilisation de mémoire dynamique doit être réduit au minimum, si possible uniquement durant les phases d'initialisations quand on peut encore dire 'Help, pas assez de mémoire".
par exemple la classe String est énormément basé sur l'allocation dynamique. Utiliser String conduit à fragmenter sa mémoire très rapidement.



N'y a t il pas de "défragmenteur" ?

Quote


Il est clair que FreeRam() ne fonctionne pas de la même façon que  freeMemory()
FreeRam() fait un simple calcul alors que freeMemory() parcours le tas pour accumuler la taille des blocs libre.

Donc je pense que freeMemory() doit donner une valeur significativement plus faible mais plus juste que freeRam().

A creuser...



Pour le moment mon projet en est à ses début. Je fais un suivi avec FreeRam en permanence et ponctuellement avec freeMemory.

D'ailleurs qu'en est il du taux d'occupation de la charge du microprocesseur ? Peut on avoir une idée comme c'est le cas pour la sram ?

Merci pour toutes ces informations qui m'éclaire mieux sur le fonctionnement de la bête.





Super_Cinci


@Super_Cinci

En C, les variables locales des fonctions sont forcement allouées "dynamiquement", sinon le code ne serait pas récursif.
Bien vu! Merci, je m'a donc trompé. Mais ça veut-il dire que si on appelle une fonction qui contient beaucoup de variables locales, on perd un temps précieux d'allocation de ces variables? ou alors c'est juste un bloc mémoire dont la taille est connue à la compilation, et quelque soit le nombre de variables, le temps d'allocation sera toujours le même (seconde hypothèse qui me paraît plus proche de la réalité...)

Quote
N'y a t il pas de "défragmenteur" ?
Il me semble avoir déjà vu traîner une question du genre, et je m'étais dit que c'était assez dangereux, car si on déplace une variable en cours d'utilisation, ça risque de par faire du bien...

Pour ce qui concerne la charge CPU, il est en général occupé à 100%, puisqu'il ne fonctionne pas en multi-tâche. Même s'il ne fait rien pendant qq µs, il tourne quand même dans des boucles d'attente (while (Serial.available() == 0); par exemple ou le CPU déclenche la fonction Serial.available() en continu et compare sa sortie à chaque tour)

J'ai quelques programmes qui tournent via des interruptions, c-à-d que loop()est vide. Dans ces programmes, dès l'entrée dans une ISR (Interrupt Sub Routine, fonction déclenchée par une interruption), je mets à 1 une pin de sortie, et je la mets à 0 à la sortie de l'ISR. Avec l'oscillo, ça me permet de voir le temps "mort" du CPU qu'il me reste pour faire des traitements globaux, ainsi que mesurer le temps de traitement desdites ISR. Mais il est rare d'en arriver à ce point.

On peut toujours utiliser ce flag matériel (mettre la pin à 0 dès qu'on entre dans une boucle d'attente par exemple) dans tout le code et mesurer la valeur moyenne de la tension sur cette pin, 0V donnera un CPU qui ne fait rien d'utile, 2.5V : traitement utile à 50%, 5V : CPU chargé à mort...

OLIVIERC67



@Super_Cinci

En C, les variables locales des fonctions sont forcement allouées "dynamiquement", sinon le code ne serait pas récursif.
Bien vu! Merci, je m'a donc trompé. Mais ça veut-il dire que si on appelle une fonction qui contient beaucoup de variables locales, on perd un temps précieux d'allocation de ces variables? ou alors c'est juste un bloc mémoire dont la taille est connue à la compilation, et quelque soit le nombre de variables, le temps d'allocation sera toujours le même (seconde hypothèse qui me paraît plus proche de la réalité...)

Quote
N'y a t il pas de "défragmenteur" ?
Il me semble avoir déjà vu traîner une question du genre, et je m'étais dit que c'était assez dangereux, car si on déplace une variable en cours d'utilisation, ça risque de par faire du bien...

Pour ce qui concerne la charge CPU, il est en général occupé à 100%, puisqu'il ne fonctionne pas en multi-tâche. Même s'il ne fait rien pendant qq µs, il tourne quand même dans des boucles d'attente (while (Serial.available() == 0); par exemple ou le CPU déclenche la fonction Serial.available() en continu et compare sa sortie à chaque tour)



Ok, donc si je comprend bien plus on lui donne de tâche à faire, moins il vérifiera le serial, les pin, etc

Quote


J'ai quelques programmes qui tournent via des interruptions, c-à-d que loop()est vide. Dans ces programmes, dès l'entrée dans une ISR (Interrupt Sub Routine, fonction déclenchée par une interruption), je mets à 1 une pin de sortie, et je la mets à 0 à la sortie de l'ISR. Avec l'oscillo, ça me permet de voir le temps "mort" du CPU qu'il me reste pour faire des traitements globaux, ainsi que mesurer le temps de traitement desdites ISR. Mais il est rare d'en arriver à ce point.

On peut toujours utiliser ce flag matériel (mettre la pin à 0 dès qu'on entre dans une boucle d'attente par exemple) dans tout le code et mesurer la valeur moyenne de la tension sur cette pin, 0V donnera un CPU qui ne fait rien d'utile, 2.5V : traitement utile à 50%, 5V : CPU chargé à mort...


ça soulève une autre question. Si le CPU est chargé à mort et qu'il rame, y a t il un risque que le cpu ne détecte pas  un appui sur un bouton (entré digitale), ou bien y a t il un tampon ?



Super_Cinci

Ah... nous voilà dans le vif d'un sujet auquel il faut faire super attention, car ça plante bien des gens :

Le fonctionnement d'un arduino n'a absolument rien à voir avec une plateforme ou OS (windows, linux...) d'un PC!

Premièrement, la résolution 8 bits contre les 32 ou 64 bits d'un PC. Il est utile de connaître précisément le traitement que l'on fait sur les variables pour savoir si on a vraiment besoin d'un INT alors qu'un byte irait très bien pour la variable X. Le traitement d'un INT (16 bits) est beaucoup plus long qu'un byte (8 bits), il est traité en deux bytes, avec les histoires de carry et tout le toutim, travailler avec une variable 16 bits prend au minimum trois fois plus de temps qu'en 8 bits. dans un for (x=0; x<20; x++), on choisira un type byte pour x, puisque sa valeur sera comprise entre 0 et 19... Dans un PC, c'est tellement rapide que le type la variable importe peu, d'autant plus qu'il n'y a pas de limitation de mémoire...

Ensuite, la gestion des ES...

Il faut considérer que chaque valeur que l'on veut traiter (état des pins, valeur de timer, données reçues sur un port série, valeur de conversion analogique...) se trouve dans une case mémoire précise (un registre) en dehors du CPU lui-même. chaque registre est en liaison permanente avec la "valeur" extérieure. par exemple, coller 5V sur la pin 3 engendrera immédiatement la valeur "1" sur la pin PIND[3], soit le registre PIND = Bxxxx1xxx, x étant la valeur précédente de chaque bit). C'est au programme d'aller chercher la valeur dans le registre au bon moment (ou le plus souvent possible) afin de récupérer ce 1 avant que le 5V disparaisse de la pin 3...
Il n'y a pas de buffer dans un arduino : les registres peuvent changer d'un moment à l'autre sans prévenir (à part via certaines interruptions). chaque fonction matérielle est un périphérique externe au CPU (même si tout ça est physiquement dans un petit cachou). A chaque périphérique sont associés plusieurs registres (adresses mémoire). Il revient au programmeur d'en vérifier de lui-même le contenu pour le traiter.

On est bien loin de la programmation sur PC où une API (OS) gère d'elle-même tous les évènements (clavier, souris, HDD...) et en mémorise les actions dans un buffer afin qu'elles ne soient pas perdues, ainsi qu'un multitâche virtuel, car les processeurs en eux même ne font pas de multitâche, au mieux, ils sont optimisés matériellement pour. Dans un simple processeur (comme l'ATMEGA), il n'y a pas d'API, car l'API est le programme en lui-même. De plus, les ressources d'un µP dans un PC sont des millions de fois plus larges que dans un arduino, permettant à l'API d'être presque invisible au programmeur.

Dans mon exemple, le Serial.available() n'est qu'un exemple, en aucun cas il ne sera vérifié si on ne rajoute pas quelque lignes de configuration dans son programme... (pas facile à expliquer)

quand je parle de mes programmes à interruption, en général, il y a plusieurs fonctions (ISR) associées à des évènements (INT externes, timers, réception séries...) qui peuvent être appelées à tout moment, et dans loop(), on ne trouve qu'une simple lecture de registres, comme un clavier, gestion d'affichage LCD... Il est évident que si mes timers s'affolent ou que le port série est en pleine ébullition, alors le CPU sera appelé très souvent pour traiter mes ISR et le traitement du loop() sera considérablement ralenti, d'où mon utilisation d'une pin en sortie qui est à 0 pendant le traitement de loop() et qui passe à 1 dès qu'on l'interrompt par une interruption. C'est à partir de là que j'optimise mes programmes (minimalisation des variables, éviter les digitalWrite ou Read...)

M'est-je fait comprendu? sinon, il y a le datasheet de l'ATMEL qui explique tout ça, mais ça demande plusieurs jours de lecture...

fdufnews

Quote
On est bien loin de la programmation sur PC où une API (OS) gère d'elle-même tous les évènements (clavier, souris, HDD...) et en mémorise les actions dans un buffer afin qu'elles ne soient pas perdues,

Dans le cas du PC c'est toujours un morceau de programme qui se charge de surveiller les entrées/sorties. Seulement comme le processeur va 100x plus vite il risque moins de louper les événements. L'API c'est juste un moyen de standardiser l'interface et de la rendre visible de toutes les applications.

OLIVIERC67


Ah... nous voilà dans le vif d'un sujet auquel il faut faire super attention, car ça plante bien des gens :

Le fonctionnement d'un arduino n'a absolument rien à voir avec une plateforme ou OS (windows, linux...) d'un PC!

...

M'est-je fait comprendu? sinon, il y a le datasheet de l'ATMEL qui explique tout ça, mais ça demande plusieurs jours de lecture...


C'est agréable de voir un forum avec des gens capable de résumer à ce point un problème complexe. Serte, il y a les datasheet, mais expliquer aussi clairement rendra la lecture du datasheet beaucoup plus efficace.
Merci à vous tous.

jihelbi

L'allocation mémoires pour les variables locales lors de l'appel d'une fonction prend toujours le même temps car c'est juste un déplacement du pointeur de pile.

Par contre l'écriture des valeurs passées en paramètre peut prendre du temps. Si les valeurs doivent êtres modifiées par la fonction il est intéressant de passer simplement un pointeur sur une structure.

JLB

skywodd

Petite précision pour agrément l'excellente explication de Super_Cinci :
Contrairement à un PC sur arduino (et plus généralement sur toute plateformes utilisant des micro-contrôleurs) il n'y as pas de notion de "charge CPU".

Sur un PC on peut ouvrir le gestionnaire de taches et ce dire "ya cette *bip* d'application qui me bouche 100% du CPU !", avec un micro-contrôleur ce n'est pas possible.
Le micro-contrôleur tourne sur un signal d'horloge (8MHz ou 16MHz en général avec les cartes arduino), à chaque "tick" de l'horloge le CPU prend une instruction depuis la mémoire flash, la décode et agit en conséquence.
Le CPU est donc toujours "à 100%" en train d'effectuer une tache, même faire une série de "nop" (instruction assembleur "no operation") revient à occuper le CPU.

En gros il n'y as pas de notion de "idle process" comme c'est le cas sur un PC.

La seul notion de "temps cpu" que l'on peut avoir est par exemple le % de cycles cpu occupé par une interruption / fonction par rapport au reste du programme.
Mais il n'existe aucune fonction pour connaitre ce "temps cpu" en temps réel, la meilleur façon de le déduire et de le mesurer avec un oscilloscope et une broche que l'on fait commuter.
Des news, des tutos et plein de bonnes choses sur http://skyduino.wordpress.com !

barbudor

JLB a déjà répondu : l'allocation est immédiate puisqu'elle se fait par le déplacement d'un pointeur dans la pile.
Et la taille de ce déplacement est connu à la compilation
MAIS LE C ne vérifie pas si la pile est assez grande.

Puisque Super_Cinci en parle, sur un processeur évolué disposant de gestion avancée de la mémoire (MMU) et surtout de mémoire paginée, la taille de la pile peut grandir automatiquement au fur et a mesure. Au risque de "bouffer" tout si tu fait une fonction récursive qui s'appelle sans fin genre :
Code: [Select]
int boufe_tout( int in )
{
  return bouffe_tout( in + 1 );
}


De même sur PC/Windows/Linux ou Mac/OSX, pas de problème pour allouer une variable globale de grande taille :
Code: [Select]
void grosse_variable()
{
  int gros_tableau[1024*1024]; // même pas mal même si tu n'as que 2MO de RAM, Windows alloue de la mémoire sur le disque...
}


Par contre sur un micro embarqué, la taille mémoire est fixe et toutes les tailles sont connues à l'avance.
Une taille fixe est allouée à la pile par l'éditeur de lien (pas facilement changeable dans l'environnement Arduino je pense) et on peut très rapidement exploser la limite si on ne fait pas attention.

Comme je le disait, par défaut le C ne vérifie pas cette possibilité (ou du moins il n'est pas spécifié qu'il doive le faire). Il existe dans certains compilateur une option de compilation que l'on peut activer si on le souhaite pendant la phase de mise au point qui va vérifier a l'entrée d'une fonction que l'on a pas dépassé la taille de la pile. Si c'est le cas : blocage de sécurité. Cette fonctionnalité est désactivée par défaut pour des raisons de performance.

Au contraire, le langage Pascal effectue généralement plus de vérification et génère un code plus sûr. Ce qui explique le grand succès de Borland et TurboPascal puis Delphi dans les années 90 et début 2000.


Pour ce qui est du "défragmenteur" (en anglais on parle de Garbagge collection (ramassage des poubelles)) ceci n'existe pas en C mais uniquement dans des environnements qui n'autorisent pas un accès par pointeurs directs mais il a surtout été popularisé dans les systèmes de machines virtuelles telles que Java et DotNet. Dans ces environnements on ne n'accède jamais à la mémoire directement par un pointeur mais par une référence qui elle contient l'adresse en mémoire. Cela autorise l'environnement à défragmenter la mémoire en tâche de fond en mettant à jour les références.

Go Up