La partie du code sur un noyau ATmega qui fait setup () et loop () est la suivante:
#include <Arduino.h>
int main(void)
{
init();
#if defined(USBCON)
USBDevice.attach();
#endif
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
Assez simple, mais il y a la surcharge de serialEventRun (); là-dedans.
Comparons deux croquis simples:
void setup()
{
}
volatile uint8_t x;
void loop()
{
x = 1;
}
et
void setup()
{
}
volatile uint8_t x;
void loop()
{
while(true)
{
x = 1;
}
}
Le x et volatile est juste pour s'assurer qu'il n'est pas optimisé.
Dans l'ASM produit, vous obtenez des résultats différents:
Vous pouvez voir le while (true) effectue juste un rjmp (saut relatif) en arrière de quelques instructions, tandis que loop () effectue une soustraction, une comparaison et un appel. Il s'agit de 4 instructions vs 1 instruction.
Pour générer ASM comme ci-dessus, vous devez utiliser un outil appelé avr-objdump. Ceci est inclus avec avr-gcc. L'emplacement varie en fonction du système d'exploitation, il est donc plus facile de le rechercher par son nom.
avr-objdump peut fonctionner sur les fichiers .hex, mais il manque la source et les commentaires d'origine. Si vous venez de construire du code, vous aurez un fichier .elf qui contient ces données. Encore une fois, l'emplacement de ces fichiers varie selon le système d'exploitation - le moyen le plus simple de les localiser est d'activer la compilation détaillée dans les préférences et de voir où les fichiers de sortie sont stockés.
Exécutez la commande comme suit:
avr-objdump -S output.elf> asm.txt
Et examinez la sortie dans un éditeur de texte.
main.c
utilisé par Arduino IDE. Cependant, cela ne signifie pas que la bibliothèque HardwareSerial est incluse dans votre croquis; en fait, il n'est pas inclus si vous ne l'utilisez pas (c'est pourquoi il y aif (serialEventRun)
dans lamain()
fonction. Si vous n'utilisez pas la bibliothèque HardwareSerial, alorsserialEventRun
sera nul, donc pas d'appel.La réponse de Cybergibbons décrit assez bien la génération de code d'assemblage et les différences entre les deux techniques. Il s'agit d'une réponse complémentaire qui examine la question en termes de différences pratiques , c'est-à-dire quelle différence l'une ou l'autre approche fera en termes de temps d'exécution .
Variations de code
J'ai fait une analyse impliquant les variations suivantes:
void loop()
(qui est en ligne lors de la compilation)void loop()
(en utilisant__attribute__ ((noinline))
)while(1)
(qui est optimisée)while(1)
(en ajoutant__asm__ __volatile__("");
. Il s'agit d'unenop
instruction qui empêche l'optimisation de la boucle sans entraîner de frais généraux supplémentaires d'unevolatile
variable)void loop()
avec optimiséwhile(1)
void loop()
avec non optimiséwhile(1)
Les croquis peuvent être trouvés ici .
Expérience
J'ai exécuté chacun de ces croquis pendant 30 secondes, accumulant ainsi 300 points de données chacun . Il y avait un
delay
appel de 100 millisecondes dans chaque boucle (sans quoi de mauvaises choses se produisent ).Résultats
J'ai ensuite calculé les temps d'exécution moyens de chaque boucle, soustrait 100 millisecondes de chacun, puis tracé les résultats.
http://raw2.github.com/AsheeshR/Arduino-Loop-Analysis/master/Figures/timeplot.png
Conclusion
while(1)
boucle non optimisée à l'intérieurvoid loop
est plus rapide qu'un compilateur optimisévoid loop
.avr-gcc
et en utilisant vos propres drapeaux d'optimisation plutôt que de dépendre de l'IDE Arduino pour vous aider (si vous avez besoin d'optimisations en microsecondes).REMARQUE: Les valeurs de temps réelles n'ont pas d'importance ici, la différence entre elles l'est. Les ~ 90 microsecondes de temps d'exécution incluent un appel à
Serial.println
,micros
etdelay
.REMARQUE 2: cela a été fait en utilisant l'IDE Arduino et les drapeaux de compilation par défaut qu'il fournit.
NOTE 3: L'analyse (tracé et calculs) a été effectuée en utilisant R.
la source