CONNEXION
  • RetourJeux
    • Sorties
    • Hit Parade
    • Les + populaires
    • Les + attendus
    • Soluces
    • Tous les Jeux
    • Gaming
  • RetourActu Gaming
    • News
    • Astuces
    • Tests
    • Previews
    • Toute l'actu gaming
  • RetourBons plans
    • Bons plans
    • Bons plans Smartphone
    • Bons plans Hardware
    • Bons plans Image et Son
    • Bons plans Amazon
    • Bons plans Cdiscount
    • Bons plans Decathlon
    • Bons plans Fnac
    • Tous les Bons plans
  • RetourJVTech
    • Actus High-Tech
    • Intelligence Artificielle
    • Smartphones
    • Mobilité urbaine
    • Hardware
    • Image et son
    • Tutoriels
    • Tests produits High-Tech
    • Guides d'achat High-Tech
    • JVTech
  • RetourCulture
    • Actus Culture
    • Culture
  • RetourVidéos
    • A la une
    • Gaming Live
    • Vidéos Tests
    • Vidéos Previews
    • Gameplay
    • Trailers
    • Chroniques
    • Replay Web TV
    • Toutes les vidéos
  • RetourForums
    • Hardware PC
    • PS5
    • Switch 2
    • Xbox Series
    • Switch
    • Pokemon pocket
    • FC 25 Ultimate Team
    • League of Legends
    • Tous les Forums
  • PC
  • PS5
  • Xbox Series
  • Switch 2
  • PS4
  • One
  • Switch
  • iOS
  • Android
  • MMO
  • RPG
  • FPS
En ce moment Genshin Impact Valhalla Breath of the wild Animal Crossing GTA 5 Red dead 2
Liste des sujets

[ASM] Trucs d'optimisation

drakenlorde
drakenlorde
Niveau 9
26 octobre 2015 à 20:25:41

Bonjour,
je suis relativement nouveau en assembleur et j'ai grand plaisir à découvrir des trucs ou des conseils pour réduire le nombre de cycle processeur tout en ayant le même résultat. Je partage ce que j'ai trouvé en espérant apprendre d'avantage en échangeant ici. J'écris tous mes exemples en syntaxe AT&T 64bit puisque c'est la méthode la plus pertinente et la plus utilisée.

Voici les trucs ou conseils que j'ai trouvés qui sont tous simple mais non négligeable :

-1 Assigner zéro à un registre
La façon normal d'assigner demande de déplacer la valeur zéro dans un registre en utilisant l'instruction MOV. Cette méthode est lente puisque le processeur va chercher la valeur zéro en mémoire avant de l'écrire au registre. Le truc est d'utiliser l'instruction XOR sur un seul registre puisque un XOR sur une même valeur est toujours égal à zéro.

Méthode MOV: movq $0,%rax
Méthode XOR: xorq %rax,%rax

-2 Instruction LEA au lieu de MOV déréférencé
Lorsqu'on veut passer l'adresse mémoire d'une variable, il semble évident de simplement utiliser MOV sur une variable déréférencer. L'instruction LEA permet de faire la même chose sans avoir à passer par l'étape de déréférencement en copie l'adresse directement.

Méthode MOV: movq $variable,%rax
Méthode LEA: leaq variable,%rax

-3 Utilisation de la soustraction pour les boucles
Dans une boucle, on a un compteur qui est comparé à chaque itération à une valeur désirée. En utilisant un compteur partant d'un nombre positif qui descend jusqu'à zéro, on peut se servir de l'état du drapeau zéro lors de l'instruction SUB et ainsi éviter une comparaison inutile.

Méthode CMP:
1.add $1,%rcx
2.cmp valeur_désirée,%rcx
3.jnz début_boucle

Méthode SUB:
1. sub $1,%rcx
2. jnz début_boucle

godrik
godrik
Niveau 30
26 octobre 2015 à 20:41:13

drakenlorde,

Note que ces micro optimisation peuvent ne pas avoir l'effet escompte. Par exemple, quand tu fais une boucle a l'envers, et si tu as des access memoire indexe sur le compteur de boucle alors tu vas faire les access vers les negatifs. Et ca, ca a tendance a perturbe certains cache.

Aussi, c'est vraiment de la micro optimisation dans le sens ou ton architecture est multiport. Donc retirer une instruction pourrait ne pas reduire le nombre de cycle necessaire pour executer un tour de boucle. Surtout si tu fais du software pipelining ou de loop unrolling qui retire deja beaucoup de comparaison.

Finalement, les vrais gains que tu peux avoir a ce niveau la, c'est d'emettre des instructions de prefetch, de s'assurer de l'utilisation d'instruction vectorielle, ou de bypasser le protocoles du cache, par exemple en bypassant "read for ownership" avec l'instruction qui va bien.

drakenlorde
drakenlorde
Niveau 9
27 octobre 2015 à 16:30:35

Dans le cas de la boucle, j'ai omis de dire que le compteur par évidement à la valeur max de la boucle. Le compteur est donc toujours positif mais fais de 15 à 0 au lieu de 0 à 15. C'est en effet une micro optimisation, mais c'est ce que fait GCC -O3 alors il doit y avoir une bonne raison.

Un autre truc qui ma été dit est d'utiliser l'instruction ET 1 au lieu de vérifier le modulo de 2 pour trouver la parité d'un nombre.

Ainsi,
andq $1,%rax
est plus rapide que
movq $2,%rbx
divq %rbx
cmpq $0,%rdx

Message édité le 27 octobre 2015 à 16:32:23 par drakenlorde
godrik
godrik
Niveau 30
27 octobre 2015 à 16:34:40

Dans le cas de la boucle, j'ai omis de dire que le compteur par évidement à la valeur max de la boucle. Le compteur est donc toujours positif mais fais de 15 à 0 au lieu de 0 à 15. C'est en effet une micro optimisation, mais c'est ce que fait GCC -O3 alors il doit y avoir une bonne raison.

Ca depend de ce qu'il y a dans la bouce. Si c'est que de l'arithmetique sur registre, c'est probablement vrai. Si c'est de l'arithmetique sur des bouts de memoire externe, tu ne peux probablement pas faire ca.

DirectX11
DirectX11
Niveau 6
27 octobre 2015 à 19:45:52

Syntaxe AT&T :malade:

drakenlorde
drakenlorde
Niveau 9
28 octobre 2015 à 16:11:02

Syntaxe AT&T :malade:

J'ai appris avec la syntaxe Intel et je lui reconnais ça "simplicité" à première vue, mais depuis que j'ai pris le temps de comprendre l'AT&T, j'ai beaucoup moins de problèmes d'ambiguïté et j'ai l'impression que l'Intel se rapproche plus d'un HLA qu'un vrai language assembleur.

Si c'est de l'arithmetique sur des bouts de memoire externe, tu ne peux probablement pas faire ca.

Étant donné la mémoire étant beaucoup plus lente à opérer que les registres, il me semble être très mauvaise pratique de mettre un compteur en mémoire. Corrige moi si je me trompe.

godrik
godrik
Niveau 30
28 octobre 2015 à 18:45:43

Si c'est de l'arithmetique sur des bouts de memoire externe, tu ne peux probablement pas faire ca.

Étant donné la mémoire étant beaucoup plus lente à opérer que les registres, il me semble être très mauvaise pratique de mettre un compteur en mémoire. Corrige moi si je me trompe.

Oui tu essayes de garder le compteur en registre (mais des fois tu prefere spiller quand meme pour gagner un registre pour l'execution du corps de la boucle.) Mais surtout le probleme est que les access memoire vont probablement dependre du compteur et changer 0->n en n->0 ca change l'ordre des acces memoires. et ca peut etre une tres mauviase chose.

drakenlorde
drakenlorde
Niveau 9
25 novembre 2015 à 21:43:11

En cherchant sur l'algorithme de permutation en assembleur, j'ai découvert l’existence de 3 méthodes différentes dont je me questionne sur la pertinence et la différence de cycle CPU.

Pour permuter les registres rax et rbx par example, on peut faire :
1. utiliser rcx en temporaire (3 instructions mov sur registre, 3 cycles CPU)
movq %rax,%rcx
movq %rbx,%rax
movq %rcx,%rbx

2. utiliser le ou exclusif (3 instructions xor sur registre, 3 cycles CPU)
xorq %rax,%rbx
xorq %rbx,%rax
xorq %rax,%rbx

3. l'instruction XCHG (1 instruction xchg sur registre, 3 cycles CPU)
xchg %rax,%rbx

Quel autre facteurs entrent-ils en jeux lors de la comparaison d’optimisation de ces 3 méthodes?
Dans les faits, y a t-il une différence entre ces méthodes et quelle est la meilleur?

kernel[]
kernel[]
Niveau 10
27 novembre 2015 à 01:27:02

Évite le xor en général à cause de certains problèmes (pipeline + ça marche hé que sur des entiers )
Sinon je pense pas que sur un cpu tu puisses dire la différence à ce niveau. Par contre l'organisation du code qui va éventuellement faire des swaps est importante.

godrik
godrik
Niveau 30
27 novembre 2015 à 18:17:35

pourquoi ca cause un prpbleme au niveau du pipeline?

kernel[]
kernel[]
Niveau 10
27 novembre 2015 à 20:32:34

Bah t'es obligé d'attendre d'avoir terminé le XOR pour commencer le suivant :(

godrik
godrik
Niveau 30
27 novembre 2015 à 21:54:17

et pouquou c'est different avec move?

dans plein de system l'alu peut prendre le resultat de l'instruction precedente comme argument. Probablement pas un intel moderne. mais avec out of order execution tu peux certainement alleger le cout.

Tikim
Tikim
Niveau 21
28 novembre 2015 à 09:41:25

C'est utile ce genre d'optimisation ? Je suis dans l'embedded et j'ai jamais entendu parler de choses de ce style. Je code en C en général mais je fais parfois de l'assembleur sur des RISC et le seul fait de coder proprement en assembleur est une optimisation suffisante. Vous avez des exemples pratiques de la ou ça fait une différence notable ? Ou de la ou ce serait nécessaire ?

godrik
godrik
Niveau 30
28 novembre 2015 à 18:06:57

bah c'est comme toujours, ca depend de ton application. ton compilateur fait tous ces trucs la de lui meme. donc les connaitre est juste utile pour arriver a lire ce qu'il produit.

en pratique ton compilateur c bien utilise ecrit du bien meilleur code que toi. donc j'ai envie de dire que c'est assez peu utile.

sur des platforme avec un compilo pourri ca peut etre utile j'imagine. (on se rappelle du cell)

Tikim
Tikim
Niveau 21
28 novembre 2015 à 18:14:40

Bon dans mon cas mon compilateur est vraiment pourri, mais c'est spécifique à mon entreprise et on peut rien y faire, à part coder en ayant en tête tous les workaround et toutes les spécificités... Typiquement je crois qu'un compilateur standard optimise mieux un switch-case qu'une suite de if/else if, mais pour mon compilateur c'est l'inverse. Dommage car en terme de lisibilité du code quand on évalue juste un enum (variable d'état) c'est le switch-case qui l'emporte.

Sous forums
  • Aide à l'achat Mac
  • Création de Jeux
  • Linux
  • Création de sites web
  • Programmation
  • Internet
  • Steam Deck
  • Macintosh
  • Hardware
La vidéo du moment