Sur la question de la vitesse et de sa mesure en Arduino



Ce problème est survenu dans l'étude des performances d'Arduino lors de l'exécution de diverses commandes (plus à ce sujet dans un article séparé). Au cours de l'étude, des doutes sont apparus quant à la constance du temps de travail des commandes individuelles lorsque la valeur des opérandes a changé (comme il s'est avéré plus tard, pas déraisonnable) et il a été décidé d'essayer d'estimer le temps d'exécution d'une commande distincte. Pour cela, un petit programme a été écrit (qui a dit que le sketch devait quitter la classe), ce qui, à première vue, a confirmé l'hypothèse. En conclusion, vous pouvez observer les valeurs 16 et 20, mais parfois 28 et même 32 microsecondes sont trouvées. Si nous multiplions les données reçues par 16 (fréquence d'horloge MK), nous obtenons le temps d'exécution en cycles MK (de 256 à 512). Malheureusement, une exécution répétée du cycle de programme principal (avec les mêmes données initiales), tout en conservant l'image globale, donne déjà une distribution différente du temps d'exécution, de sorte que les variations de temps réelles ne sont pas liées aux données initiales. L'hypothèse d'origine est réfutée, mais elle devient intéressante, et ce qui est exactement associé à une telle dispersion significative.

Remarque nécessaire - Je comprends très bien que des programmes plus sophistiqués devraient être utilisés pour mesurer le temps d'exécution des commandes, mais pour une estimation approximative, celui qui sera démontré plus tard est tout à fait suffisant.

Donc, le temps change, et de manière très significative, nous recherchons les causes de ce phénomène. Tout d'abord, nous prêtons attention à la multiplicité des valeurs obtenues, regardons la description de la bibliothèque de travail au fil du temps et voyons que 4µsec est un quantum de mesure, il est donc préférable d'aller aux quanta et de comprendre que nous obtenons 4 ou 5 (très souvent) et 6 ou 7 ou 8 unités (très rares). Avec la première moitié, tout est facile - si la valeur mesurée se situe entre 4 et 5 unités, alors la dispersion devient inévitable. De plus, en considérant les échantillons comme indépendants, nous pouvons augmenter la précision de mesure par des méthodes statistiques, ce que nous faisons, en obtenant des résultats acceptables.

Mais avec la seconde moitié (6,7,8), les choses sont pires. Nous avons constaté que la dispersion n'est pas corrélée avec les données source, ce qui signifie qu'il s'agit d'une manifestation d'autres processus qui affectent le temps d'exécution des commandes. Il est à noter que les émissions sont plutôt rares et ne montrent pas d'effet significatif sur la valeur moyenne calculée. Il serait possible de les négliger du tout, mais ce n'est pas notre style. En général, au cours des années de travail en ingénierie, j'ai réalisé que vous ne pouvez pas laisser de malentendus, aussi insignifiants soient-ils, car ils ont une capacité dégoûtante de battre dans le dos (enfin, ou où ils atteignent) au moment le plus inopportun.

Nous commençons à émettre l' hypothèse 1 - la plus pratique (en termes de commodité et de polyvalence, elle est juste derrière l'intervention directe du Créateur) - les problèmes logiciels, bien sûr, pas les miens, mes programmes ne manquent jamais, mais les bibliothèques connectées (compilateur, système d'exploitation, navigateur, etc. - remplacer le nécessaire). De plus, puisque j'exécute le programme dans l'émulateur sur www.tinkercad.com , vous pouvez toujours vous référer aux bogues de l'émulateur et fermer le sujet, car le code source n'est pas disponible pour nous. Inconvénients de cette hypothèse:

  1. De cycle en cycle, l'emplacement des écarts change, ce qui laisse entendre.
  2. Ce site prend toujours en charge AutoDesk, bien que l'argument soit plutôt faible.
  3. "Nous avons accepté le postulat que ce qui se passe n'est pas une hallucination, sinon ce serait tout simplement inintéressant."

L'hypothèse suivante est l'influence de certains processus d'arrière-plan sur le résultat de la mesure. Nous ne semblons rien faire d'autre que croire, cependant ... nous produisons les résultats en série. L'hypothèse 2 se pose - le temps de sortie est parfois (aussi étrange que ça ... mais ça arrive) est ajouté au temps d'exécution de la commande. Bien qu'il soit douteux de savoir combien cette sortie est là, mais de toute façon - l'ajout de Flush n'a pas aidé, l'ajout d'un délai pour terminer la sortie et cela n'a pas aidé, ce qui a généralement fait sortir la sortie de la boucle - de toute façon, le temps saute - ce n'est certainement pas Serial.

Eh bien, ce qui reste, c'est l'organisation du cycle lui-même (d'où l'effroi de changer sa durée, ce n'est pas clair) et c'est tout ... bien que micros () soit resté. Je voulais dire que le temps d'exécution du premier appel de cette fonction et du second est le même et lorsque je soustrais ces deux valeurs, j'obtiens zéro, mais si cette hypothèse est fausse?

Hypothèse 3 - parfois le deuxième appel du décompte du temps prend plus de temps que le premier, ou les actions associées au décompte du temps affectent parfois le résultat. Nous regardons le code source de la fonction de travail avec le temps (arduino-1.8.4 \ hardware \ arduino \ avr \ cores \ arduino \ câblage.c - j'ai exprimé à plusieurs reprises mon attitude envers de telles choses, je ne me répéterai pas) et nous voyons que 1 fois sur 256 les cycles d'augmentation matérielle de la partie la plus jeune du compteur sont interrompus pour incrémenter la partie la plus ancienne du compteur.

Notre temps d'exécution de cycle est de 4 à 5, nous pouvons donc nous attendre à 170 * (4..5) / 256 = de trois à quatre valeurs anormales dans un segment de 170 mesures. Nous regardons - cela ressemble beaucoup, il y en a vraiment 4. Pour séparer les première et deuxième raisons, nous effectuons des calculs par la section critique avec des interruptions interdites. Le résultat ne change pas grand-chose, les émissions ont encore leur place, ce qui signifie que du temps supplémentaire est apporté par les micros (). Ici, nous ne pouvons rien faire, bien que le code source soit disponible, nous ne pouvons pas le changer - les bibliothèques sont incluses dans les binaires. Bien sûr, nous pouvons écrire nos propres fonctions de travail avec le temps et surveiller leur comportement, mais il existe un moyen plus simple.

Étant donné qu'une raison possible de l'augmentation de la durée est le traitement d'interruption «longue», nous excluons la possibilité de son apparition pendant le processus de mesure. Pour ce faire, attendez sa manifestation et ce n'est qu'ensuite que nous effectuons un cycle de mesure. Comme l'interruption se produit beaucoup moins souvent que notre cycle de mesure ne dure, nous pouvons garantir son absence. Nous écrivons le fragment correspondant du programme (en utilisant des hacks sales avec des informations extraites du code source) et, «c'est une telle magie de la rue», tout devient normal - nous mesurons le temps d'exécution de 4 et 5 quanta avec une valeur moyenne du temps d'exécution de l'opération d'addition avec PT de 166 cycles d'horloge, qui correspond à la valeur précédemment mesurée. L'hypothèse peut être considérée comme confirmée.

Une dernière question demeure - et qu'est-ce qui prend si longtemps dans les interruptions, que faut-il
(7.8) - (5) ~ 2 quanta = * 4 = 8 msec * 16 = 128 cycles de processeur? Nous nous tournons vers le code source (c'est-à-dire le code assembleur généré par le compilateur sur godbolt.com) et nous voyons que l'interruption elle-même est exécutée environ 70 cycles, 60 d'entre eux en permanence, et lors de la lecture, il y a des coûts supplémentaires de 10 cycles, 70 au total lorsque frappé interruption - moins que reçue, mais assez proche. Nous attribuons la différence à la différence entre les compilateurs ou les modes d'utilisation.

Eh bien, nous pouvons maintenant mesurer le temps d'exécution réel de la commande d'addition PT avec divers arguments et nous assurer qu'il change vraiment beaucoup lorsque les arguments changent: de 136 mesures pour 0,0 à 190 pour 0,63 (nombre magique), et ce n'est que 162 pour 10,63. Avec une probabilité de 99,9%, cela est dû au besoin d'alignement et aux caractéristiques de sa mise en œuvre dans cette bibliothèque particulière, mais cette étude dépasse clairement la portée du problème considéré.

Annexe - texte du programme:
void setup() { Serial.begin(9600); } volatile float t; //   void loop() { int d[170]; unsigned long time,time1; float dt=1/170.; for (int i=0; i<170; ++i) { { //       time1=micros(); long time2; do { time2=micros(); } while ((time2 & ~0xFF) == (time1 & ~0xFF)); }; /**/ time1=micros(); //   /* cli(); //       -   */ t=10.63; //     t=t+dt; //   /* sei(); //    */ time = micros(); //   time1=time-time1; d[i]=time1/4; /* Serial.print(time1); //      Serial.flush(); //     Delay(20); //    */ }; //   ,     float sum=0; for (int i=0; i<170; ++i) { sum+=d[i]; Serial.println(d[i]); }; Serial.println((sum/170-2.11)*4*16); //2.11Serial.flush(); //    ,     } 

Source: https://habr.com/ru/post/fr426941/


All Articles