Différence d'optimisation entre deux fonctions

a marqué ce sujet comme résolu.

Bonjour,

Je dois optimiser un code pour un microcontrôleur embarqué (atmega128, mcu 8 bits) et j’ai remarqué une différence d’optimisation entre deux fonctions et je ne comprend pas cette différence. Voici le code C:

unsigned int MulLInt(unsigned int a, unsigned int b) {
    unsigned char a1 = (unsigned char)(a&0xFF);
    unsigned char b1 = (unsigned char)(b&0xFF);
   return a1*b1 ;
}

unsigned int MulEInt(unsigned int a, unsigned int b) {
    unsigned char a1 = (unsigned char)((a>>8)&0xFF);
    unsigned char b1 = (unsigned char)((b>>8)&0xFF);
   return a1*b1 ;
}

En le compilant avec avr-gcc et les options -mmcu=atmega128 -Ofast, j’obtiens le code assembleur suivant :

MulLInt:
    mul r22,r24
    movw r24,r0
    clr __zero_reg__
    ret
MulEInt:
    mov r18,r25
    ldi r19,0
    mov r24,r23
    ldi r25,0
    movw r20,r24
    mul r18,r20
    movw r24,r0
    mul r18,r21
    add r25,r0
    mul r19,r20
    add r25,r0
    clr r1
    ret

Je ne vous ai pas tout mis parce que le reste n’a, je pense, pas d’intérêt dans ce problème. La fonction MulLInt n’a qu’une seule multiplication (on fait une multiplication de deux octets donc c’est attendu) alors que la fonction MulEInt, pour la même multiplication, effectue 3 mul. Pourquoi cette différence ?

Merci pour vos réponses.

Bonjour,

La première multiplication est la bonne. Ensuite il ajoute r18 x r21, mais ici r21 est nul (par movw r20,r24, r21 est la copie de r25 qui a été mis à 0) Ensuite il ajoute r19 x r20, alors que r19 a clairement été mis à 0. C’est en effet curieux d’ajouter deux fois 0, l’optimiseur n’est pas fortiche!

+1 -0

Toute opération sur des nombres inférieurs à int est malgré tout forcée sur des un/signed int. C’est la norme. Autant dire que le cast vers des unsigned char ne sert donc à rien, et fort heureusement le compilo voit que c’est le cas et ne semble pas faire payer.

Les deux multiplications en plus, c’est entre le poids fort de a>>8 (i.e. 0), et le faible de b>>8, et vice versa. Techniquement, pour une multiplication sur des nombres sur deux registres (c’est ce que je comprends de l’analyse de l’assembleur), cela fait sens. Sauf que le compilo devrait savoir que les poids forts sont à 0 et donc que les opérations dessus ne servent à rien.

C’est bizarre, sur goldbot, avec d’autres cibles ou sans le -mmcu=..., le compilo s’en sort bien mieux. Ca mériterait un bug report AMA.

Sauf que le compilo devrait savoir que les poids forts sont à 0

Ce n’est pas ce que je comprends du bout de code MulEInt

Pour moi, il prend des int en arguments, les poids forts ne sont donc pas nécessairement à 0. D’ailleurs, la fonction MulEInt multiplie les 2e octets des arguments (décalage, puis masque, puis cast) là ou la fonction MulLInt multiplie les 1er octets.

Après, je n’ai pas essayé de lire l’assembleur, c’est pas vraiment mon langage favoris :-°

Pour moi, il prend des int en arguments, les poids forts ne sont donc pas nécessairement à 0

Je décode qu’il prend des ints 16 bits, avec (a>>8) && 0xFF, on mets les poids forts dans les faibles et on RAZ les poids forts. Pour la suite des calculs qui nous intéressent, les poids forts sont à 0, et le compilo devrait le savoir, mais l’assembleur nous montre que ce n’est pas le cas.

Après, je n’ai pas essayé de lire l’assembleur, c’est pas vraiment mon langage favoris :-°

Moi non plus. Mais le code est assez simple ici, avec avec le support de godbolt qui indique que fait quel opcode avec une fonction de hover et des dessins sur un papier, ce n’est pas insurmontable.

Bref, on retrouve ce que je disais avec

  • a sur r25..24, qui finit avec r25 dans r18 et r19 à 0 ; c’est atmp == (a>>8) & 0xFF.
  • Pour b, il arrive dans r23..22, mais il est stocké en tant que btmp == (b>>8) & 0xFF dans r25..24 où il y avait a. (Cet espace est d’ailleurs là où il y aura la sortie dans la fonction.) Et il redéplace le tout dans r21..20 — je ne vois pas trop pourquoi il y a cette étape intermédiaire.
  • Et ensuite, commencent les multiplications:
    • r18 (faible de atmp) x r20 (faible de btmp) dans r24 (sortie, faible), et j’imagine que le débord finit dans r25
    • et les croisées inutiles:
      • r21 (fort btmp, == 0) * r18 (faible atmp), soit 0 accumulé dans r25 (sortie, fort)
      • r19 (fort atmp, == 0) * r20 (faible btmp), soit 0 accumulé dans r25 (sortie, fort)

Et on retourne donc r25..24

Sur godbolt, gcc9.2.0 produisait un résultat légèrement différent

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte