Une boucle infinie à l'intérieur de loop () fonctionnerait-elle plus rapidement?

19

Lorsque vous écrivez un croquis typique, vous comptez généralement loop()être appelé à plusieurs reprises tant que l'Arduino est en cours d'exécution. Entrer et sortir de la loop()fonction doit cependant introduire une petite surcharge.

Pour éviter cela, vous pouvez probablement créer votre propre boucle infinie, comme ceci:

void loop()
{
    while (true)
    {
        // do stuff...
    }
}

Est-ce un moyen viable d'améliorer les performances? Cela causera-t-il d'autres problèmes s'il loop()ne revient jamais?

Peter Bloomfield
la source

Réponses:

18

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: Comparaison de deux

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.

Cybergibbons
la source
OK, mais n'y a-t-il aucune raison d'appeler la fonction serialEventRun ()? Pourquoi est-ce?
jfpoilpret
1
Cela fait partie des fonctionnalités utilisées par HardwareSerial, je ne sais pas pourquoi elles ne sont pas supprimées lorsque Serial n'est pas nécessaire.
Cybergibbons
2
Il serait utile d'expliquer brièvement comment vous avez généré la sortie ASM pour que les gens puissent se vérifier eux-mêmes.
jippie
@Cybergibbons, il n'est jamais supprimé car il fait partie du standard main.cutilisé 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 a if (serialEventRun)dans la main()fonction. Si vous n'utilisez pas la bibliothèque HardwareSerial, alors serialEventRunsera nul, donc pas d'appel.
jfpoilpret
1
Oui, il fait partie du main.c comme cité, mais je m'attendrais à ce qu'il soit optimisé s'il n'est pas requis, donc je pense que les aspects de Serial sont toujours inclus. J'écris fréquemment du code qui ne reviendra jamais de loop () et ne remarque aucun problème avec Serial.
Cybergibbons
6

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:

  • Basique void loop()(qui est en ligne lors de la compilation)
  • Non aligné void loop()(en utilisant __attribute__ ((noinline)))
  • Boucle avec while(1)(qui est optimisée)
  • Boucle avec non optimisé while(1)(en ajoutant __asm__ __volatile__("");. Il s'agit d'une nopinstruction qui empêche l'optimisation de la boucle sans entraîner de frais généraux supplémentaires d'une volatilevariable)
  • Un non aligné void loop()avec optimiséwhile(1)
  • Un non aligné 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 delayappel 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

  • Une while(1)boucle non optimisée à l'intérieur void loopest plus rapide qu'un compilateur optimisé void loop.
  • La différence de temps entre le code non optimisé et le code optimisé par défaut Arduino est pratiquement insignifiante . Vous feriez mieux de compiler manuellement en utilisant avr-gccet 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, microset delay.

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.

asheeshr
la source
1
Bon travail. Le graphique a des millisecondes pas des microsecondes mais pas un gros problème.
Cybergibbons
@Cybergibbons C'est assez improbable car toutes les mesures sont en microsecondes et je n'ai pas changé d'échelle 'n'importe où :)
asheeshr