Est-ce normal que le code généré soit si mauvais ?

Bonjour,
Je commence à utiliser l'Arduino et je suis impressionné par le nombre de drivers disponibles pour toute sorte de périphériques et par la portabilité des projets (je suis passé instantanément d'un Arduino "de base" à un teensy sans rien changer, c'est vraiment plaisant) .
Cependant, je suis par ailleurs assez effaré par l'inefficacité du code généré sur les cibles avr (par exemple x >> 14 génère une boucle de 14 décalages à gauche, les multiplications 32=16x16 ne sont jamais généré efficacement quelque soit les casts que l'on utilise, etc.).

  • Y a-t-il des optimisations à mettre en oeuvre quelque part pour avoir un code un peu plus convenable ?
  • Le compilateur gcc utilisé avec d'autres plateformes est-il aussi inefficace ?
  • Y a-t-il des documentations qui expliquent comment est faite la backend génération dans gcc avr et comment la modifier ?

Cordialement
Jean-Louis

Bonjour,

jlvern:
Cependant, je suis par ailleurs assez effaré par l'inefficacité du code généré sur les cibles avr (par exemple x >> 14 génère une boucle de 14 décalages à gauche, les multiplications 32=16x16 ne sont jamais généré efficacement quelque soit les casts que l'on utilise, etc.).

Sans exemple concret il va être difficile de donner une réponse. Dans ce genre de cas « l'efficacité » du code généré dépend grandement des propriétés affectées à chaque variables, entres autres : s'agit-il de constantes dont la valeur est connue à la compilation, sont-elles déclarées volatiles, etc.

jlvern:

  • Y a-t-il des optimisations à mettre en oeuvre quelque part pour avoir un code un peu plus convenable ?

Oui, mais ça dépend de ce qu'on entend par « code convenable »... Encore une fois, des exemples précis (Source C / code généré par le compilateur) permettraient de mieux comprendre ce que tu cherches à faire.

jlvern:

  • Y a-t-il des documentations qui expliquent comment est faite la backend génération dans gcc avr et comment la modifier ?

Pas compris la question, désolé :slight_smile:

Bonjour,

Toute l'info devrait se trouver dans la doc et les sources de GCC.

As-tu essayé avec différentes options d'optimisation différentes ? Ce que tu trouves mauvais est peut-être le code le plus performant par rapport aux options d'optimisation choisies.

gcc est un des meilleurs compilo, mais tout dépend des options choisies. Arduino est à vocation "large", et qui dit souplesse en prog dit souvent lourdeur ...

Ok, par exemple j'écris :

uint32_t shift15( uint32_t x )
{
return x >> 15;
}

uint16_t mulfractu16u16( uint16_t x, uint16_t y )
{
return (uint16_t)( ((uint32_t)x * (uint32_t)y) >> 16 );
}

voici le code généré pour shift15() (les variables sont tout ce qu'il y a de plus standard, pas register, pas volatile, rien) :
15f0: 8d 81 ldd r24, Y+5 ; 0x05
15f2: 9e 81 ldd r25, Y+6 ; 0x06
15f4: af 81 ldd r26, Y+7 ; 0x07
15f6: b8 85 ldd r27, Y+8 ; 0x08
15f8: 3f e0 ldi r19, 0x0F ; 15
15fa: b6 95 lsr r27
15fc: a7 95 ror r26
15fe: 97 95 ror r25
1600: 87 95 ror r24
1602: 3a 95 dec r19
1604: d1 f7 brne .-12 ; 0x15fa <loop+0xac>
1606: 89 83 std Y+1, r24 ; 0x01
1608: 9a 83 std Y+2, r25 ; 0x02
160a: ab 83 std Y+3, r26 ; 0x03
160c: bc 83 std Y+4, r27 ; 0x04

et voici le code généré pour mulfractu16u16()

160e: 61 96 adiw r28, 0x11 ; 17
1610: ae ad ldd r26, Y+62 ; 0x3e
1612: bf ad ldd r27, Y+63 ; 0x3f
1614: 61 97 sbiw r28, 0x11 ; 17
1616: bd 01 movw r22, r26
1618: 80 e0 ldi r24, 0x00 ; 0
161a: 90 e0 ldi r25, 0x00 ; 0
161c: 63 96 adiw r28, 0x13 ; 19
161e: ee ad ldd r30, Y+62 ; 0x3e
1620: ff ad ldd r31, Y+63 ; 0x3f
1622: 63 97 sbiw r28, 0x13 ; 19
1624: 9f 01 movw r18, r30
1626: 40 e0 ldi r20, 0x00 ; 0
1628: 50 e0 ldi r21, 0x00 ; 0
162a: 0e 94 89 2a call 0x5512 ; 0x5512 <__mulsi3>
162e: bc 01 movw r22, r24
1630: 88 27 eor r24, r24
1632: 99 27 eor r25, r25
1634: 80 e0 ldi r24, 0x00 ; 0
1636: 90 e0 ldi r25, 0x00 ; 0
1638: 69 87 std Y+9, r22 ; 0x09
163a: 7a 87 std Y+10, r23 ; 0x0a
163c: 8b 87 std Y+11, r24 ; 0x0b
163e: 9c 87 std Y+12, r25 ; 0x0c

0our shift15, je ne trouve pas que décaler de 15 fois à droite en décalant 15 fois à droite soit tout à fait pertinent (j'aurais bien vu un seul décalage à gauche, mais bon...)

Pour la multiplication fractionnelle c'est idem. Le compilateur ne comprend pas que caster deux nombres de 16 bits en 32bits avant une multiplication indique qu'il n'a pas besoin de faire une multiplication 32x32, mais qu'il peut faire une multiplication 32=16x16.

J'ai d'autres exemples du même tonneau qui montre que les opérations de bas niveau sont très mal codées (du moins avec le niveau d'optimisation dans lequel je suis).

Je voulais réécrire les fonctions trigonométriques de base (atn, sin) pour les remplacer par des fonctions utilisant des entiers. J'ai été assez étonné car je n'arrivais à un gain de vitesse que dans un rapport de 2 ou 3 par rapport au flottant alors que je m'attendais à un rapport au moins de 5 à 10 (si je me fie à d'autres implémentations). Toutes les routines d'optimisation en entier font appel à des opérations de scaling et à des multiplications dont les résultats sont souvent de type différent des types des opérandes. Si les opérations de bases sont mal codées, ces optimisations sont très difficiles à réaliser.

Optimiser le code en faisant des "procedural abstraction" c'est bien mais si x>>15 est codé aussi mal, ça ne sert pas à grand chose... enfin, c'est ce que je pense...

D'où mon désarrois.

Et donc pour en revenir à la question de base, est-il possible de changer quelque chose, dans l'IDE Arduino (ou à coté) pour améliorer les optimisations ?

Pour haifger, je ne suis pas très clair, je m'en excuse. "backend" ou "frontend" dans un compilateur, c'est souvent là ou le code (presque) final est effectivement généré (presque parce qu'il y a surement des passes supplémentaires pour virer les trucs qui ne servent à rien).

Je ne suis pas d'accord avec toi pour ton histoire de multiplication, car le compilateur fait exactement ce que tu demandes : des casts en 32 bits, puis un décalage, puis un autre cast.

Pour le reste, tu n'as pas répondu à ma question : quelles options de compilation as-tu mises ? Il en existe qui développent les boucles, d'autres qui cherchent la solution la plus rapide ou la plus petite...

Je pense que tu assumes que le compilateur fait ceci ou cela, alors qu'il implémente ce que le standard et ce que l'équipe de GCC a décidé de faire. Prends le temps d'aller lire leur documentation, de leur poser des questions sur leur canal IRC ou mailing list, et tu auras des réponses précises à tes questions.

Et si tu as des propositions d'amélioration d'avr-gcc, propose-leur tes patches.

Ok, alors pour la faire courte, je pense qu'il y a une erreur de casting sur l'environnement de développement choisi. L’environnement Arduino est principalement destiné aux débutants qui ont besoin de quelque chose de très basique permettant de produire immédiatement des résultats sans avoir à mettre les mains dans le cambouis. Comme l'a dit B@tto juste au-dessus, cette approche possède des inconvénients, surtout lorsque l'on veut se lancer dans la micro-optimisation comme cela semble être ton cas. Entres autres :

  • la version du compilateur fournie avec l'IDE est « vieille » (donc non optimale), c'est particulièrement vrai si tu es sous Windows.
  • il n'est pas possible (ou disons plutôt assez difficile) de changer les paramètres du compilateur.

À partir de ce constat tu as plusieurs solutions pour t'en sortir, mais elles dépendent grandement de ton système d'exploitation, il va donc falloir nous en dire plus.

Pour en revenir à tes exemples :

jlvern:
0our shift15, je ne trouve pas que décaler de 15 fois à droite en décalant 15 fois à droite soit tout à fait pertinent (j'aurais bien vu un seul décalage à gauche, mais bon...)

C'est un défaut « connu » de gcc, il ne sait pas (ou mal) optimiser les décalages qui ne sont pas des multiples de 8. Dans ce cas précis, en réécrivant la fonction comme ça:

uint32_t shift15(uint32_t x) {
  return ((x >> 8) << 1) >> 8);
}

la version 4.7.2 de gcc génère un code assez proche de l'optimum :

push  r16
push  r17
mov   r16, r23
mov   r17, r24
mov   r18, r25
eor   r19, r19
add   r16, r16
adc   r17, r17
adc   r18, r18
adc   r19, r19
mov   r16, r17
mov   r17, r18
mov   r18, r19
eor   r19, r19
movw  r22, r16
movw  r24, r18
pop   r17
pop   r18
ret

jlvern:
Pour la multiplication fractionnelle c'est idem. Le compilateur ne comprend pas que caster deux nombres de 16 bits en 32bits avant une multiplication indique qu'il n'a pas besoin de faire une multiplication 32x32, mais qu'il peut faire une multiplication 32=16x16.

Encore une fois c'est probablement un problème de version du compilateur. Avec la 4.7.2 le code généré fait appel à la fonction __umulhisi3 plutôt qu'à __mulsi3 comme dans ton cas : il a donc bien vu l'optimisation possible et fait effectivement une multiplication de type 32 = 16 x 16.

jlvern:
J'ai d'autres exemples du même tonneau qui montre que les opérations de bas niveau sont très mal codées (du moins avec le niveau d'optimisation dans lequel je suis).

Les fonctions de bas niveau ne sont pas mal codées du tout, dans ce cas précis c'est le compilateur qui a loupé une optimisation possible, ça n'a rien à voir.

jlvern:
Je voulais réécrire les fonctions trigonométriques de base (atn, sin) pour les remplacer par des fonctions utilisant des entiers. J'ai été assez étonné car je n'arrivais à un gain de vitesse que dans un rapport de 2 ou 3 par rapport au flottant alors que je m'attendais à un rapport au moins de 5 à 10 (si je me fie à d'autres implémentations). Toutes les routines d'optimisation en entier font appel à des opérations de scaling et à des multiplications dont les résultats sont souvent de type différent des types des opérandes. Si les opérations de bases sont mal codées, ces optimisations sont très difficiles à réaliser.

Le mieux à faire si tu es certain d'avoir identifié un chemin d'optimisation que le compilateur n'a pas vu c'est de faire un rapport de bogue vers gcc. Évite juste de leur asséner jusqu'à plus soif que « les opérations de base sont mal codées », c'est loin d'être un bon moyen d'engager une discussion.

jlvern:
Optimiser le code en faisant des "procedural abstraction" c'est bien mais si x>>15 est codé aussi mal, ça ne sert pas à grand chose... enfin, c'est ce que je pense...
D'où mon désarrois.
Et donc pour en revenir à la question de base, est-il possible de changer quelque chose, dans l'IDE Arduino (ou à coté) pour améliorer les optimisations ?

Comme je le disais au début, le problème se situe dans le choix de tes outils. Tu cherches à couper un arbre avec un couteau à beurre. Ce n'est pas impossible, mais c'est quand même très peu pratique. Il est souvent possible d'optimiser pour la taille du code ou pour la vitesse d'exécution mais pas forcément les deux à la fois. Dans la même veine, simplicité d'utilisation ne fait pas forcément bon ménage avec contrôle total de toute la chaîne de compilation.

Petit ajout, ceci est aberrant :

1630:   88 27          eor   r24, r24
1632:   99 27          eor   r25, r25
1634:   80 e0          ldi   r24, 0x00   ; 0
1636:   90 e0          ldi   r25, 0x00   ; 0

je serai curieux de savoir quels sont le code source/compilateur/options qui ont conduit à ça.

Quelle version de avr-gcc utilises-tu ? As-tu essayé avec une version récente, comme la 4.8.4 ou la 4.9 ?

Bonjour à tous, et merci pour vos réponses.

En ce qui concerne la version, il semble que ce soit "gcc version 4.3.2 (WinAVR 20081205).

Pour recentrer ma question, je ne connais pas gcc avr, et je n'avais jamais utilisé l'environnement Arduino et les Arduino. Je ne me suis donc pas cassé la tête, j'ai téléchargé la version courante (pas beta) de l'IDE Arduino et point barre. Je suis très intéressée par le coté polyvalent du système, et par l'environnement software qu'il a généré (en particulier au niveau du développement des drivers de capteurs). Je voulais juste voir si ça serait utilisable professionnellement, sans trop me casser la tête.

Je voulais donc voir ce que l'on pouvait faire avec l'Arduino, et la première chose que je fais, en général, quand j'ai un compilateur, c'est de regarder comment il compile... et je n'ai pas trouvé ça bien terrible, voilà tout, en prenant le truc "out of the box". D'où ma question de savoir si on pouvait améliorer la sauce, d'une manière ou d'une autre, si possible en restant simple (genre mettre un -o5 ou -os ou -je ne sais quoi quelque part).

Ayant la flemme (et pas trop le temps) de lire les 3 milliards de pages de la doc gcc je me suis adressé au ci-présent forum pour avis de personnes plus compétentes que moi en a matière. J'ai trouvé quelques informations aussi ici Google Code Archive - Long-term storage for Google Code Project Hosting. que je n'ai pas encore eu le temps de regarder en détail (mais ça viendra).

En ce qui concerne les optimisations des compilateurs, je ne comprends pas très bien le discours qui consiste à dire que les optimisations sont faites pour les pro, et que les amateurs doivent avoir un système simple, et donc (ce n'est pas moi qui le dit) pas optimisé. C'est comme si on disait qu'il vaut mieux, pour un jeune conducteur, commencer avec une Ford modèle T qu'on démarre à la manivelle.
Je comprends très bien que IAR, Keil (ou Microchip) propose des version d'essai avec les options de compilations bridées pour vendre celui qui compile correctement, c'est leur business, mais je comprends mal que l'on tienne le même discours avec un compilateur domaine public (discours tenu aussi par l'équipe Arduino).
Je sais que vous me direz qu'il y a plusieurs type d'optimisation, que l'on peut choisir ceci ou cela (size or speed), etc., mais à mon avis tout ceci est un peu de l'embrouille, sauf le respect que je vous dois.
J'ai utilisé des compilateurs Keils, IAR, etc, je mets tout le temps le niveau max d'optimisation et je m'en porte très bien. Evidemment je ne vais pas lui demander de faire un unroll de toutes le boucles (et en général il ne le fait pas) Le seul cas que j'ai noté dans lequel l'absence d'optimisation est bénéfique, c'est les cas où on gère mal les "volatile" (mais c'est parce que on les gère mal -pas-). Je ne comprends donc tout simplement pas pourquoi il est bénéfique pour un "amateur" d'avoir un code lent et gros (c'est quand même souvent lié) quand il pourrait avoir un code plus rapide et plus petit...

A vrai dire, cette histoire énerve un peu (mais pas vous, je vous assure), parce que c'est vraiment caractéristique de l'air du temps. Il y a une vingtaine d'année, quand on lisait Dr Doobs ou Computer Langage les concepteurs de compilateurs se battaient à coup de benchmark pour être celui qui faisait le sieve ou le printf le plus court et le plus rapide. Actuellement il semble que l'on se foute plus du code généré que de savoir si c'est basé sur Eclipse, parce qu'après tout, si ce n'est pas assez rapide, on prendra un micro plus gros...

Pour en revenir à nos moutons après cette digression politico-socio-technique, je ne veux insulter personne en disant que le code est naze (voire aberrant comme le dit haifger ;-), mais il me semble malgré tout que dans la configuration dans laquelle j'ai testé, ce soit péremptoirement le cas.

Je vois que XavierMiller me demande si j'ai testé une version récente, il semble (cf haifger) que la version "out of the box" ne le soit pas... Peut-on installer simplement une version récente de gcc simplement de telle manière qu'elle soit prise en compte par l'IDE Arduino (en ne dépassant pas 500mg d'aspirine) ? Ou inversement y a-t-il un environnement pas trop usine à gaz (pitié, pas Eclipse, plutôt dans le genre Code::Blocks) qui permette de récupérer les sketch arduino sans trop de problème ? Un truc qui tournerait sur PC et Mac serait bien, mais sur PC seul Zindows m'irait... Un truc simple à installer... C'est ça qui me plaisait sur Arduino, on télécharge et ça marche (lentement).

nb (à XavierMiller) : il est probable que l'équipe gcc n'ait pas besoin de moi, et il est vraisemblable que les "problèmes" que je rencontre ne soient dû qu'à une question de niveau d'optimisation, mais j'avais collaboré il y a des lustres à SDCC (justement pour la génération de code)...

Javais noté que dans l'utilisation des tableaux, le compilateur recalcule entièrement l'adresse d'une "case", même dans le cas de tableau[4]+tableau[5], alors qu'il suffisait d'incrémenter le pointeur de RAM. J'ai essayé de mettre à jour le compilateur, mais sans succès, les chemins des fichiers à compiler contenant des espaces ne passaient plus dans les paramètres de avr-gcc.

Donc on est mal barrés...

Si en plus on se limite à la plateforme arduino, où TOUS les exemples et libs font appel à des int 16 bits pour numéroter ce qui dépasse rarement 20, on comprend qu'on est loin d'utiliser la puissance réelle du processeur.

J'étais content de voir qu'on pouvait multiplier par 20 la vitesse d'exécution du digitalWrite en faisant de l'accès direct au port concerné, mais très déçu de voir que pour le portB du 328 qui est dans la zone adressable directe, bas le traitement est le même qu'un registre "distant", donc perte encore de temps...

Bref, il y a du boulot, bon courage dans ta quête!

PS : il reste la possibilité d'écrire certaines parties sensibles directement en assembleur... un code bien pondu sur un 328 à 16MHz pourrait faire pleurer un code arduino sur la DUE...

jlvern:
En ce qui concerne les optimisations des compilateurs, je ne comprends pas très bien le discours qui consiste à dire que les optimisations sont faites pour les pro, et que les amateurs doivent avoir un système simple, et donc (ce n'est pas moi qui le dit) pas optimisé. C'est comme si on disait qu'il vaut mieux, pour un jeune conducteur, commencer avec une Ford modèle T qu'on démarre à la manivelle.
Je comprends très bien que IAR, Keil (ou Microchip) propose des version d'essai avec les options de compilations bridées pour vendre celui qui compile correctement, c'est leur business, mais je comprends mal que l'on tienne le même discours avec un compilateur domaine public (discours tenu aussi par l'équipe Arduino).

La question n'est pas la : Arduino est une plateforme qui englobe plusieurs µC et se veut simple, fiable et généralisable(qui saura le µC qu'on pourra programmer demain ?). Ce qui veut dire au niveau compilation, absence de certaines optimisations qui pourrait conduire à plus de bug et ne risque pas de poser des problème sur d'autres µC. En aucun cas il s'agit de brider l'utilisateur, mais bien de lui simplifier la vie. C'est ce que je diais au début : simplification ou optimisation, il y a un choix. Arduino étant destinée à un public non averti, il est de bon aloi de privilégier la première option.

jlvern:
A vrai dire, cette histoire énerve un peu (mais pas vous, je vous assure), parce que c'est vraiment caractéristique de l'air du temps. Il y a une vingtaine d'année, quand on lisait Dr Doobs ou Computer Langage les concepteurs de compilateurs se battaient à coup de benchmark pour être celui qui faisait le sieve ou le printf le plus court et le plus rapide. Actuellement il semble que l'on se foute plus du code généré que de savoir si c'est basé sur Eclipse, parce qu'après tout, si ce n'est pas assez rapide, on prendra un micro plus gros...

La aussi tu te trompes de cible : Arduino simplifie énormément de choses. Tu veux allumer une led à moitié ? analogWrite(Pin,128) et c'est régler. Pas besoin de se taper plein de registre et toute la doc du µC ... Mais évidement on en revient au même, c'est pas optimisé niveau vitesse. Mais c'est pas le but d'Arduino. Soit tu proposes des voitures où même ton grand père rentre dedans facilement mais qui ne vont pas très vite, soit t'as une ferrari ... Et papy se brisera les reins avant de s'asseoir au volant ...

Et puis prendre un micro plus gros : et alors ? Au bout d'un moment faut pas rester dans des carcans qui ne changent rien dans le fond.

Bonjour,
Parfaitement incompétent sur le sujet en question mais, néanmoins, curieux de la technique en question et lecteur assidu du topic.
Cela me parait être un combat d'arrière garde car celui qui cherche le dernier quart de mico-seconde dans son programme peut très bien utiliser d'autres outils de compilation ou alors l'assembleur.
L'assembleur à l'avantage, si ça ne va pas assez vite, c'est que le programmeur n'est pas dans le bon algorithme.
Il y a des développeurs qui adorent se servir du stylo à octets :wink:
Pas sur la tête, je suis déjà parti. :slight_smile:
:grin:

GCC 4.3 est quand même assez ancien... essaie une version plus récente.