Dans l’objectif de mieux évaluer les performances de la programmation en C pour Arduino, j’ai écris ce petit programme:
unsigned long t[8]; // t[n] marqueurs temporels
void setup() {
int i, j;
pinMode(2, INPUT);
pinMode(3,OUTPUT);
Serial.begin(115200);
t[0] = micros();
// code dont on veux mesurer le temps d'exécution
t[1] = micros(); // on mesure l'instant t ou le code c'est exécuté
for (i = j = 0; i < 1000; i++) { // exemple ajouter 1000 fois une constante à un entier
j += 3;
}
t[2] = micros();
for (i = 0; i < 1000; i++) { // autre exemple écrire sur une sortie 1000 fois
digitalWrite(3, LOW);
}
t[3] = micros();
for (i = 0; i < 1000; i++) {
digitalRead(2);
}
t[4] = micros();
for (i = 0; i < 1000; i++) {
delayMicroseconds(1);
}
t[5] = micros();
for (i = 0; i < 1000; i++) {
delayMicroseconds(2);
}
t[6] = micros();
for (i = 0; i < 1000; i++) {
delayMicroseconds(3);
}
t[7] = micros();
for (i = 1; i <= 7; i++) { // on affiche le résultat
Serial.print("temps ");
Serial.print(i);
Serial.print(": ");
Serial.println(t[i] - t[i-1]); // différence de temps entre les deux marqueurs = temps d’exécution
}
}
void loop() {
}
Le principe consiste à écrire différents bouts de code dont on veux tester la vitesse d’exécution, en les encadrant entre deux marqueurs temporels, en utilisant la fonction micro().
Note: la fonction micros() renvoie le temps écoulé depuis le dernier reset en multiple de 4.
Voici le résultat:
temps 1: 4
temps 2: 4
temps 3: 5284
temps 4: 4152
temps 5: 1320
temps 6: 2080
temps 7: 3084
temps 1: 4 // 4 microsecondes, normal il n’y a pas de code.
temps 2: 4 // On incrémente 1000 fois une constante à la variable j, en seulement 4 microsecondes soit 64 cycles???
C’est ce résultat qui pose problème, dont j’aimerais avoir l’explication.
temps 3: 5284 // rien à redire sinon que c’est beaucoup plus lent qu’en assembleur, normalement un cycle (*1000 plus la boucle) de mémoire
temp 4: // idem
temps 5: // 1320 microsecondes contre environ 1000 attendu.
temps 2: 4 // On incrémente 1000 fois une constante à la variable j, en seulement 4 microsecondes soit 64 cycles???
C'est ce résultat qui pose problème, dont j'aimerais avoir l'explication.
La variable n'est pas utilisée ailleurs dans le programme. Il est fort probable que le compilateur l'aura supprimée ainsi que le code associé. Il faudrait faire un Serial.print de la variable J à la fin de la boucle comme ça le code ne serait pas optimisé.
La fonction micros() prend elle même du temps . Cela ne gêne peut-être pas dans une première approche mais sache que la fonction micros() est basé sur un timer (le timer0 il me semble) plus tout une tripaille logicielle notamment pour absorber plusieurs débordement de compteur
Quand j'ai eu à faire ce genre de manip j'ai utilisé le timer2 avec un réglage du " préscaler" à 1. J'aurais pu prendre aussi le Timer0 mais j'ai préféré éviter de modifier sa configuration.
En faisant cela il suffit de lire le contenu du registre TCNTx, c'est à dire pour le timer2 TCNT2, et tu as directement une lecture en cycles horloge, c'est à dire que le pas est maintenant de 1/16000000= 62,5 ns -> c'est plus précis que micro() .
Pour ne pas avoir à gérer les débordements de compteur comme le fait la fonction micro() il suffit de faire une raz sur le compteur TCNT2 juste avant de début de la partie de code à mesurer.
Exemple:
uint8_t temps ;
/*****
Code
*****/
TCNT2 = 0 ;
/******************
code à mesurer
*****************/
temps = TCNT2 ;
La lecture de la partie de la datasheet qui traite des timers est super enrichissante et c'est fou ce que l'on peut faire avec.
fdufnews:
La variable n'est pas utilisée ailleurs dans le programme. Il est fort probable que le compilateur l'aura supprimée ainsi que le code associé. Il faudrait faire un Serial.print de la variable J à la fin de la boucle comme ça le code ne serait pas optimisé.
J’ai testé le code et j’obtiens toujours 4µs.
J’ai ajouté:
j = digitalRead(2);
for (i = 0; i < 1000; i++) {
j+=3;
}
et j’obtiens 8µs.
J’ai ensuite ajouté une condition:
j = digitalRead(2);
for (i = 0; i < 1000; i++) {
if (j == 0) j+=3;
}
et j’obtiens 696µs.
J’en ai déduit que le compilateur dans les premiers cas, remplaçait la boucle
for (i = 0; i < 1000; i++) {
j+=3;
}
par
j += 3000;
i = 1000;
Lorsque j’ai ajouté la condition "if (j == 0)", le compilateur a compilé la boucle.
C’est plus complexe que je ne le pensais.
Je pensais que le compilateur se contentait bêtement de compiler la boucle en instructions machines (j’avais un doute sur la fonction micros()).
68tjs:
La fonction micros() prend elle même du temps . Cela ne gêne peut-être pas dans une première approche mais sache que la fonction micros() est basé sur un timer (le timer0 il me semble) plus tout une tripaille logicielle notamment pour absorber plusieurs débordement de compteur
Oui. Par curiosité j’ai mesuré 3µs pour l’appel de la fonction micros();
68tjs:
En faisant cela il suffit de lire le contenu du registre TCNTx, c'est à dire pour le timer2 TCNT2, et tu as directement une lecture en cycles horloge, c'est à dire que le pas est maintenant de 1/16000000= 62,5 ns -> c'est plus précis que micro()
[...]
La lecture de la partie de la datasheet qui traite des timers est super enrichissante et c'est fou ce que l'on peut faire avec.
Je ne connaissais pas. Intéressant tous ça. Merci pour ces précisions, je vais me pencher dessus.
Lorsque tu as compilé ton code (sans forcément le téléverser), la fenêtre de log t'indique dans quel répertoire ça a pondu des fichiers. Tu peux alors y aller, et trouver le .hex pondu : c'est lui qui sera envoyé à l'arduino.
J'ai utilisé un soft qui transforme le .hex en assembleur. C'est pas très lisible, mais en prenant son temps, on finit par découvrir de jolies choses, comme des abus de compilation, des rajouts qui ne sont pas nécessaires etc etc... c'est très instructif!
le soft que j'utilise est "reAVR" de ja-tools, j'arrive pas à le retrouver sur la toile, mais il doit y en avoir un paquet d'autres!
Super_Cinci:
J'ai utilisé un soft qui transforme le .hex en assembleur. C'est pas très lisible, mais en prenant son temps, on finit par découvrir de jolies choses, comme des abus de compilation, des rajouts qui ne sont pas nécessaires etc etc... c'est très instructif!
Merci pour le lien, j’ai téléchargé le soft.
J’avais déjà remarqué l’embonpoint du programme blink. Ca doit prendre 20 ou 30 instructions en assembleur
L’éditeur Arduino ne propose pas la sauvegarde et le téléchargement du fichier binaire.
La sauvegarde peut se faire manuellement en allant dans le répertoire temporaire, mais le téléchargement à partir d’un binaire?