La carte Arduino Uno a une mémoire RAM limitée, ce qui signifie qu'elle dispose d'une pile d'appels limitée. Parfois, la récursivité est la seule option rapide pour implémenter un certain algorithme. Donc, étant donné que la pile d'appels est sévèrement limitée, quelle serait une façon de savoir qu'étant donné un certain programme exécuté sur la carte, combien d'appels récursifs pouvez-vous vous permettre avant qu'il y ait un débordement de pile (et que de mauvaises choses se produisent)?
programming
sram
asheeshr
la source
la source
How much ca!@#QFSD@$RFW
:? Je suis curieux de savoir pourquoi personne n'a édité cela pour être quelque chose de plus significatif (au cours des 4 dernières années).211
fois (en fonction de nombreux facteurs) :). Voir ma réponse ici: arduino.stackexchange.com/a/51098/7727 . @ NickGammon, il fait semblant de "maudire" je pense. C'est un jeu de mot pour "recurse". Cela m'a pris une minute pour comprendre cela aussi. C'était assez déroutant au début.Réponses:
Si vous voulez vraiment recurer (et comme @jippie l'a dit, c'est une mauvaise idée; message subliminal: ne le faites pas ) et que vous voulez savoir combien vous pouvez recurer, alors vous devrez effectuer des calculs et des expériences; vous n'en aurez généralement qu'une approximation car cela dépend beaucoup de l'état de la mémoire au moment où votre fonction récursive sera appelée.
Pour cela, vous devez d'abord savoir comment la SRAM est organisée dans l'Arduino basé sur AVR (cela ne s'appliquera pas, par exemple, à l'Arduino Galileo d'Intel). Le schéma suivant d'Adafruit le montre clairement:
Ensuite, vous devez connaître la taille totale de votre SRAM (dépend du microcontrôleur Atmel, d'où le type de carte Arduino que vous avez).
Sur ce diagramme, il est facile de déterminer la taille du bloc de données statiques tel qu'il est connu au moment de la compilation et ne changera pas plus tard.
La taille du segment de mémoire peut être plus difficile à connaître car elle peut varier au moment de l'exécution, selon les allocations de mémoire dynamique (
malloc
ounew
) effectuées par votre croquis ou les bibliothèques qu'il utilise. L'utilisation de la mémoire dynamique est assez rare sur Arduino, mais certaines fonctions standard le font (le type l'String
utilise, je pense).Pour la taille de la pile , elle variera également pendant l'exécution, en fonction de la profondeur actuelle des appels de fonction (chaque appel de fonction prend 2 octets sur la pile pour stocker l'adresse de l'appelant) et du nombre et de la taille des variables locales, y compris les arguments passés ( qui sont également stockés sur la pile ) pour toutes les fonctions appelées jusqu'à présent.
Supposons donc que votre
recurse()
fonction utilise 12 octets pour ses variables et arguments locaux, puis chaque appel à cette fonction (le premier d'un appelant externe et les appels récursifs) utilisera des12+2
octets.Si nous supposons que:
recurse()
fonction est appelée à partir de votre esquisse, la pile actuelle fait 128 octets de longIl vous reste alors des
2048 - 132 - 128 = 1788
octets disponibles sur la pile . Le nombre d'appels récursifs à votre fonction est donc1788 / 14 = 127
, y compris l'appel initial (qui n'est pas récursif).Comme vous pouvez le voir, c'est très difficile, mais pas impossible de trouver ce que vous voulez.
Un moyen plus simple d'obtenir la taille de pile disponible avant l'
recurse()
appel serait d'utiliser la fonction suivante (trouvée sur le centre d'apprentissage Adafruit; je ne l'ai pas testée moi-même):Je vous encourage fortement à lire cet article au centre d'apprentissage Adafruit.
la source
.bss
représente les variables globales sans valeur initiale dans votre code, alors quedata
c'est pour les variables globales avec une valeur initiale. Mais au final, ils utilisent le même espace: les données statiques dans le diagramme.static
dans une fonction.La récursivité est une mauvaise pratique sur un microcontrôleur comme vous l'avez déjà dit vous-même et vous voulez probablement l'éviter autant que possible. Sur le site Arduino, il y a quelques exemples et bibliothèques disponibles pour vérifier la taille de la RAM libre . Vous pouvez par exemple l'utiliser pour déterminer quand rompre la récursivité ou un peu plus délicat / plus risqué pour profiler votre croquis et coder en dur la limite. Ce profil serait requis pour chaque modification de votre programme et pour chaque modification de la chaîne d'outils Arduino.
la source
Cela dépend de la fonction.
Chaque fois qu'une fonction est appelée, une nouvelle trame est poussée sur la pile. Il contiendra généralement divers éléments critiques, notamment:
this
) si vous appelez une fonction membre.Comme vous pouvez le voir, l'espace de pile requis pour un appel donné dépend de la fonction. Par exemple, si vous écrivez une fonction récursive qui ne prend qu'un
int
paramètre et n'utilise aucune variable locale, elle n'aura pas besoin de beaucoup plus que quelques octets sur la pile. Cela signifie que vous pouvez l'appeler récursivement bien plus qu'une fonction qui prend plusieurs paramètres et utilise beaucoup de variables locales (ce qui consommera la pile beaucoup plus rapidement).Évidemment, l'état de la pile dépend de ce qui se passe d'autre dans le code. Si vous lancez une récursivité directement dans la
loop()
fonction standard , il n'y aura probablement pas déjà beaucoup de choses sur la pile. Cependant, si vous le lancez imbriqué plusieurs niveaux profondément dans d'autres fonctions, il n'y aura pas autant de place. Cela affectera le nombre de fois que vous pouvez récéder sans épuiser la pile.Il convient de noter que l'optimisation de la récursivité de queue existe sur certains compilateurs (même si je ne suis pas sûr que avr-gcc le supporte). Si l'appel récursif est la toute dernière chose dans une fonction, cela signifie qu'il est parfois possible d'éviter de modifier du tout le cadre de pile. Le compilateur peut simplement réutiliser le cadre existant, puisque l'appel «parent» (pour ainsi dire) a fini de l'utiliser. Cela signifie que vous pouvez théoriquement continuer à récurer autant que vous le souhaitez, tant que votre fonction n'appelle rien d'autre.
la source
J'avais exactement la même question que je lisais Jumping into C ++ par Alex Allain , Ch 16: Recursion, p.230, j'ai donc fait quelques tests.
TLDR;
Mon Arduino Nano (mcu ATmega328) peut faire 211 appels de fonction récursifs (pour le code donné ci-dessous) avant qu'il y ait un débordement de pile et des plantages.
Tout d'abord, permettez-moi de répondre à cette affirmation:
[Mise à jour: ah, j'ai survolé le mot "rapide". Dans ce cas, vous avez une certaine validité. Néanmoins, je pense que cela vaut la peine de dire ce qui suit.]
Non, je ne pense pas que ce soit une vraie déclaration. Je suis pratiquement certain que tous les algorithmes ont une solution récursive et non récursive, sans exception. C'est juste que parfois c'est beaucoup plus faciled'utiliser un algorithme récursif. Cela dit, la récursivité est très mal vue pour une utilisation sur les microcontrôleurs et ne serait probablement jamais autorisée dans le code critique pour la sécurité. Néanmoins, il est bien sûr possible de le faire sur des microcontrôleurs. Pour savoir à quel point vous pouvez entrer dans une fonction récursive donnée, testez-la! Exécutez-le dans votre application réelle dans un cas de test réel, et supprimez votre condition de base afin qu'elle se reproduise à l'infini. Imprimez un compteur et voyez par vous-même jusqu'où vous pouvez aller "en profondeur" pour savoir si votre algorithme récursif repousse les limites de votre RAM trop près pour être utilisé pratiquement. Voici un exemple ci-dessous pour forcer le débordement de pile sur un Arduino.
Maintenant, quelques notes:
Le nombre d'appels récursifs ou de "trames de pile" que vous pouvez obtenir est déterminé par un certain nombre de facteurs, notamment:
free_RAM = total_RAM - stack_used - heap_used
ou vous pourriez direfree_RAM = stack_size_allocated - stack_size_used
)Mes résultats:
Segmentation fault (core dumped)
#pragma GCC optimize ("-O0")
en haut du fichier et refaites:Here are the final print results: 209 210 211 ⸮ 9⸮ 3⸮
Le code:
L'application PC:
Le programme Arduino "Sketch":
Les références:
#pragma GCC optimize
commande depuis que je savais que je l'avais documenté là-bas.la source
#pragma
utiliser ce que vous y utilisez. Au lieu de cela, vous pouvez ajouter__attribute__((optimize("O0")))
à la fonction unique que vous souhaitez désactiver.J'ai écrit ce programme de test simple:
Je l'ai compilé pour l'Uno, et au moment où j'écris, il s'est reproduit plus d'un million de fois! Je ne sais pas, mais le compilateur a peut-être optimisé ce programme
la source
call xxx
/ret
parjmp xxx
. Cela revient au même, sauf que la méthode du compilateur ne consomme pas la pile. Ainsi, vous pourriez récuser des milliards de fois avec votre code (toutes choses étant égales par ailleurs).#pragma GCC optimize ("-O0")
en haut de votre programme Arduino. Je crois que vous devez le faire en haut de chaque dossier auquel vous souhaitez qu'il s'applique - mais je n'ai pas recherché cela depuis des années, alors recherchez-le par vous-même pour en être sûr.