samedi 4 octobre 2014

GCC, optimisation et portabilité

Je travaille présentement sur un projet utilisant un atMega328 avec Atmel studio 6. Dans cet article je discute des différents niveaux d'optimisation utilisés par GCC (GNU C Compiler) ainsi que des problèmes de portabilité causé par la taille variable du type de donnée int.

Optimisation

Atmel studio comme MPLABX utilise GCC comme compilateur et par défaut lorsqu'on cré un projet le niveau d'optimisation est -Os. À ce niveau GCC tente de créer le code le plus compacte possible. Comme les microcontrôleurs sont en général limités en mémoire, c'est logique d'utiliser cette optimisation. Donc lors de la première compilation de mon projet j'ai obtenu un code très compact. Malheureusement ça ne fonctionnais pas. Le projet utilise une routine de service d'interruption qui doit avoir un délais de réponse rapide (low latency), mais le résultat n'était pas à la hauteur des besoins. Après avoir examiner le code assembleur (fichier *.lss dans output files du projet), j'ai été sidéré de voir la quantité de code utilisé en préambule par la routine. C'est là que j'ai décidé de changer le niveau d'optimisation pour -O3 dans les propriétés du projet. Là ça fonctionnais mais la taille du code avait grossis plus que je ne l'aurais cru!

Voici la taille du code généré pour mon projet (non complété) selon les différents niveaux d'optimisation:

  • -O0 Ne compile pas! affiche l'avertissement suivant:
    Warning 8 #warning "Compiler optimizations disabled; functions from <util/delay.h> won't work as designed"
  • -O1
    Program Memory Usage : 4652 bytes 14,2 % Full
    Data Memory Usage : 1604 bytes 78,3 % Full
  • -O2
    Program Memory Usage : 4728 bytes 14,4 % Full
    Data Memory Usage : 1604 bytes 78,3 % Full
  • -O3
    Program Memory Usage : 5514 bytes 16,8 % Full
    Data Memory Usage : 1604 bytes 78,3 % Full
  • -Os
    Program Memory Usage : 4434 bytes 13,5 % Full
    Data Memory Usage : 1604 bytes 78,3 % Full

Donc lorsqu'on veut optimiser pour la taille on utilise -Os. Mais si on veut optimiser pour la vitesse on utilise -O1,-O2 ou -O3. Chacun prenant plus d'espace de code. Cette augmentation du code est du au fait que pour augmenter la vitesse le compilateur transforme certaines sous-routines en inline même si on ne l'a pas spécifié et aussi il peut dérouler certaines boucles simple avec petit nombre de répétitions.

Au final j'ai décidé de compiler mon projet en -O2, à ce niveau le délais de l'ISR étant suffisamment cours.

Portabilité du code

Comme je ne veut pas avoir à réécrire le même code chaque fois que je change de microcontrôleur je réutilise des fichiers d'autres projets. C'était le cas pour ce projet. Mais lorsque j'ai adapter le code j'ai eu quelques problèmes du au fait que le type de donnée int peut varié de taille d'une plateforme à l'autre. Ainsi sur PIC32MX un int est 32 bits mais sur un AVR il est de 16 bits.

Pour rendre un code source 'C' plus portable il est préférable de préciser la taille des entiers utilisés et dans ce but d'inclure #include <stdint.h> dans le projet. Ce fichier contient des définitions de différentes tailles d'entiers tel que:

  • int8_t entier sur 8 bits et sa version non signé uint8_t.
  • int16_t entier sur 16 bits et sa version non signé uint16_t.
  • int32_t entier sur 32 bits et sa version non signé uint32_t.
En utilisant ces types d'entiers au lieu de simplement utiliser int on rend le code plus facile à porter d'une plateforme à l'autre. Autre avantage c'est qu'on peut sauver de l'espace RAM. En effet si je sais que la valeur de ma variable ne dépassera pas 255 je peut utilisé uint8_t au lieu de unsigned int qui va occupé 2 octets de RAM. Et de plus comme les AVR sont des MCU 8 bits les opérations sur les int demandes plus d'instructions. Donc sur un MCU 8 bits utilisé int8_t ou uint8_t lorsque le domaine de la variable le permet, sauve de l'espace RAM, de l'espace de code et augmente la rapidité des opérations sur cette variable.

Aucun commentaire:

Enregistrer un commentaire