30

beersleep :
Juste une petite question, c'est avec quel logiciel/desassembleur que tu vérifies la sortie en asm? smile


Je demande simplement au compilo de sortir le listing assembleur.

La syntaxe differe suivant le compilateur, pour GCC il faut utiliser -S dans la ligne de commande pour lui dire de compiler sans assembler..

Faire un petit gcc --help pour voir tous les arguments dispo

developpeur de jeux mobiles chez int13 production --- http://int13.net

31

Je suis curieux de savoir avec quel compilateur tu compiles parce que je n'est pas le même résultat, pourtant j'ai mis exactement le même code ?

32

J'ai utilisé le meilleur compilo que j'ai sous la main, donc pas celui du GNU mais celui de Microsoft (celui fourni gratuitement avec Visual Studio 2005)

Etonnant non ?

Sinon les resultats de GCC sont moins bons que celui de MS, la fonction clear 1 est compilée en 12 instructions (contre 6 pour le compilo MS) et la fonction clear2 est compilée en 18 instructions, avec sauvegarde sur la pile et deux acces memoire pour charger des constantes (vraiment pas top!).

Dans tous les cas l'optimisation de coderman s'avère contre productive !

Pour clear1 GCC se debrouille pour compter à l'envers, mais pour seulement pour la boucle externe... Etrange...

J'avoue être un peu déçu par les mauvaises perfs de GCC (version 4.0.2) qui est pourtant assez reputé..

Je vais chercher un peu pour voir si ya pas moyen de le forcer à mieux compiler.

EDIT : dans les deux cas (clear1 & clear2) la inner loop fait 4 instructions avec GCC, et 3 avec le compilo MS.

developpeur de jeux mobiles chez int13 production --- http://int13.net

33

Voila ce que j'ai obtenu.

clear1
MOV a3,#0
|L00000c.J4.clear1|
MOV a2,#0
|L000010.J5.clear1|
STR a2,[a1],#4
ADD a2,a2,#1
CMP a2,#0x140
BLT |L000010.J5.clear1|
ADD a3,a3,#1
CMP a3,#0xf0
BLT |L00000c.J4.clear1|
MOV pc,lr
===================================
clear2
MOV a3,#0xee
ADD a4,a3,#0x50
|L000038.J4.clear2|
MOV a2,a4
|L00003c.J7.clear2|
STR a2,[a1],#4
SUBS a2,a2,#1
BCS |L00003c.J7.clear2|
SUBS a3,a3,#1
BCS |L000038.J4.clear2|
MOV pc,lr

"Clear 1" possède 2 test de comparaison CMP (lent) "clear 2" n'en possède pas, ce qui n'est pas rien dans un boucle répété 76800 !

Donc je ne vois pas trop pourquoi t'obstiner a prouver que ce petit bout de code et inutile, le but n'est pas de prouver que tel ou tel compilateur est plus performant., mieux vaut opter pour cette mini optimisation d'une manière "standard" suivant les compilateurs, pour les programmeurs non experimenté c'est que du bénéf et ca ne fait aucun mal.

34

Euh, ce n'est pas le même code C que celui que j'ai utilisé smile

Et d'ailleurs j'ai pas l'impression que clear1 et clear2 fassent la même chose.

Sinon je veux bien savoir que compilateur tu as utilisé et avec quels parametres le nommage des registres et le fait que les instructions soit sorties en upper case ne ressemble pas à gcc.

Peut être un desassembleur ?

Quand tu dis que ça ne fait aucun mal, relis mes posts, dans tous mes tests la version "optimisée" n'etait pas plus rapide, et avait systematiquement plus d'instructions machines (donc code plus gros et un peu lent).

Je ne sais pas quel version de gcc tu as utilisé, mais j'ai au moins 3 cas ou l'effet est contre productif : compilo MS, GCC 4.02, et GCC 2.9.5 (compilé en -O2)

De plus le fait de compter "à l'envers" est une manip pouvant facilement generer des bugs chez les programmeurs distraits (ça rend le code plus tordu si les compteurs sont utilisés à l'interrieur de la boucle), perso je laisse ce genre de trucs au compilo, et en dernier recours je le fais à la main en asm quand c'est necessaire.

Sans compter le fait que si il s'agit de lire dans la memoire, il est fortement deconseillé de le faire à l'envers, le cache est optimisé pour les acces lineaires sequentiels dans le sens des adresses croissantes, en sens inverse on se tape le temps de latence complet de la ligne de cache lors de chaque cache miss...
developpeur de jeux mobiles chez int13 production --- http://int13.net

35

le compilateur c'est ARM STD avec l'option -o2 -S et je t'affirme que c'est le même code
et c'est bien la même chose si tu interprète mentallement le code asm (bon les registres sont noté différement mais c'est pas de ma faute )
Autre chose, j'ai déjà essayé ce genre d'optimisation par le passé et, dans de grosses boucle, crois moi, c'est legerement plus rapide et ca se vois.

void clear1(long *dst) 
{ 
long i, j; 

for(j = 0; j < 240; j++) 
for(i = 0; i < 320; i++) 
{ 
*dst++ = i; 
} 
} 

void clear2(long *dst) 
{ 
long i, j; 

for(j = 239; j--; ) 
for(i = 319; i--; ) 
{ 
*dst++ = i; 
}


je vais essayer demain avec le gcc franchement, j'ai un leger doute.

Maintenant, je propose à UKKO histoire de recentrer un peu le sujet de tester avec un code "concret" par lui même dans les deux cas. Et ca serait mieux d'avoir un indice de rapiditer sous forme de chiffre.

36

Alors non, j'insiste ce n'est pas le même code que celui que j'ai fourni en exemple :

il y a une difference entre *dst++ = 0; et *dst++ = i;

D'ailleurs clear1 et clear2 NE FONT PAS la même chose, tu vois quand je parlais de programmeurs distraits..

clear1 donne une sequence :

0 1 2 3 ... 319
0 1 2 3 ... 319
.
.
0 1 2 3 ... 319

clear2 donne :

319 318 ... 0
319 318 ... 0
.
.
319 318 ... 0


Il faudrait remplacer *dst++ =i par *dst++ = 319 - i dans clear2, ce qui ferait donc au moins une instruction de plus... -sorry-

---

Sinon tu voulais dire ARM SDT et pas STD non ?

Comment peux on se procurer ce compilateur ?
developpeur de jeux mobiles chez int13 production --- http://int13.net

37

arf, zut, j'ai noté la dernier compilation. je te met celui avec le 0 mais ca ne change pas grand chose, la structure et la même.

clear1
        MOV      a3,#0
|L00000c.J4.clear1|
        MOV      a2,#0
|L000010.J5.clear1|
        STR      a2,[a1],#4
        ADD      a2,a2,#1
        CMP      a2,#0x140
        BLT      |L000010.J5.clear1|
        ADD      a3,a3,#1
        CMP      a3,#0xf0
        BLT      |L00000c.J4.clear1|
        MOV      pc,lr

clear2
        MOV      a3,#0xee
        ADD      a4,a3,#0x50
|L000038.J4.clear2|
        MOV      a2,a4
|L00003c.J7.clear2|
        STR      a2,[a1],#4
        SUBS     a2,a2,#1
        BCS      |L00003c.J7.clear2|
        SUBS     a3,a3,#1
        BCS      |L000038.J4.clear2|
        MOV      pc,lr

D'ailleurs clear1 et clear2 NE FONT PAS la même chose, tu vois quand je parlais de programmeurs distraits..

qu'entends tu pas ne font pas la même chose? tu peux développer ?
ARM SDT tu voulais dire je pense ?

oui bien sur faute de frappe
Comment peux on se procurer ce compilateur ?

Il etait dispo en téléchargement il y a bien quelques années, maintenant, je ne pense plus, a moins de chercher.

38

clear1 donne une sequence :

0 1 2 3 ... 319
0 1 2 3 ... 319
.
.
0 1 2 3 ... 319

clear2 donne :

319 318 ... 0
319 318 ... 0
.
.
319 318 ... 0


Il faudrait remplacer *dst++ =i par *dst++ = 319 - i dans clear2, ce qui ferait donc au moins une instruction de plus... -sorry-
---


heu je te suis plus là, On bien là pour tester la "différence" entre "2 procedures" non ?

Le but est de tester la différence entre for(j = 0; j < 240; j++) et for(j = 239; j--; ) non? c'est bien la le début de toute une histoire

Donc, le 319-i n'as pas de raison d'être ici etant donné ( je le reredit pour les autres ) que cette optimisation est a utiliser si le code dans le for en question n'as pas d'ordre précis.

39

en faite c'etait le bon code asm, j'ai mal noté le code C il fallait bien lire *dst++ = 0; et non *dst++ = i; smile

40



Donc, le 319-i n'as pas de raison d'être ici etant donné ( je le reredit pour les autres ) que cette optimisation est a utiliser si le code dans le for en question n'as pas d'ordre précis.


Eh bien dans ce cas il faut bien mettre *dst++ = constante ou un truc independant de i.

Le code asm que tu as publié est (etait car tu l'as peut etre edité depuis ?) bien celui correspondant au code C avec *dst++ = i.

Pour tester une optimisation entre deux procedures il faut evidement que les deux fassent EXACTEMENT la même chose, sinon ça n'a pas de sens.

developpeur de jeux mobiles chez int13 production --- http://int13.net

41

CoderMan :
en faite c'etait le bon code asm, j'ai mal noté le code C il fallait bien lire *dst++ = 0; et non *dst++ = i; smile


Non.

developpeur de jeux mobiles chez int13 production --- http://int13.net

42

hier soir j'ai tapé -o2 alors que le o devait être en majuscule, du coup, il a crée un fichier nommé "2"...Je vais mettre ca sur le compte de la fatigue ( trop de fêtes, pas assez dormis ) ...mea culpa.

Voila enfin le bon code de cette optimisation de la mort qui tue(c)

clear1
        MOV      a2,#0
        MOV      a4,#0
|L000010.J4.clear1|
        MOV      a3,#0
|L000014.J5.clear1|
        ADD      a3,a3,#1
        CMP      a3,#0x140
        STR      a4,[a1],#4
        BLT      |L000014.J5.clear1|
        ADD      a2,a2,#1
        CMP      a2,#0xf0
        BLT      |L000010.J4.clear1|
        MOV      pc,lr

clear2
        STR      lr,[sp,#-4]!
        MOV      a3,#0xee
        ADD      ip,a3,#0x50
        MOV      a4,#0
|L000044.J4.clear2|
        MOV      a2,ip
|L000048.J7.clear2|
        SUBS     a2,a2,#1
        STR      a4,[a1],#4
        BCS      |L000048.J7.clear2|
        SUBS     a3,a3,#1
        BCS      |L000044.J4.clear2|
        LDR      pc,[sp],#4


Pour tester une optimisation entre deux procedures il faut evidement que les deux fassent EXACTEMENT la même chose, sinon ça n'a pas de sens.


La par contre, tu fais preuve de mauvaise fois parceque tout le discoure se situe autour de " for(j = 0; j < 240; j++) " ET " for(j = 239; j--; ) sans ordre précis pour le traitement j" je rereprécis pour les autres on sait jamais smile

43

Bon juste pour vous faire chier... non je rigole bien sûr... Mais pour résumer...

Dans une inner loop il vaut mieux faire :
exemple avec un tableau de structures bidons (balls) :

for (i = balls_tot - 1; i--; )
{
  Fais_des_trucs_cools_sur_la balle(&balls[i]);
}


que

for (i = 0; i < balls_tot; i++)
{
  Fais_des_trucs_cools_sur_la balle(&balls[i]);
}


Pour résumer c'est bien cela ???

Le gain doit être minime ??
Le gain est-il toujours réel ??
Cela fonctionne-t-il avec tous les compilo ??
Le gain est-il uniquement lié au fait qu'il est moins coûteux une fois transcrit en assembleur de comparer un entier avec 0 qu'avec une valeur ??

Merci d'avance de m'éclairer...

44

Le gain est du au fait que lorsque on decremente un registre et que sa valeur atteint zero cela permet d'eviter une instruction de comparaison.

De maniere generale il est conseillé de privilegier les tests avec 0 que les tests avec une autre constante.

-----------

Voici comment calculer le gain theorique :


Soit N le nombre de cycles machine que prend ce qui se trouve à l'interieur de la boucle.

Le gain en pourcent vaut G = (100 / N)

Donc pour une boucle extremement courte, par exemple 5 instructions, le gain sera de 20%

Des que la boucle s'aggrandit, le gain diminue et devient vite negligeable (30 instructions -> 3.3 cheeky

Surtout que le programmeur à tout interet à derouler sa boucle (entre 4 et 16 fois) si celle si est aussi courte que ça, ce qui permet deux optimisations :

1 - On n'incremente (ou decremente) le compteur de boucle qu'une fois sur 4 ou sur 16
2 - On evite de casser le pipeline du CPU lors des jumps, ce qui fait perdre plusieurs cycles (le temps de recharger entierement le pipeline)


Et puis en plus certains compilos le font eux même, et produiront du code moins bon quand on force cette optimisation.

Pour les boucles deroulées je vous conseille une recherche sur "Duff's Device" dans Google.







developpeur de jeux mobiles chez int13 production --- http://int13.net

45

Cool... Merci des précisions Steph...