[ASM x86] Critique d'un programme avec NASM

Bonjour,

Je sais que le forum doit être utilisé pour résoudre des problèmes de programmation mais j’ai besoin de personnes qui s’y connaissent et qui peuvent critiquer un code source afin de l’amélioré.

Voilà, j’ai tappé ce petit programme en assembleur (avec NASM) afin de vérifier si un mot entré par l’utilisateur est un palindrome ou non (chose que l’on a tous fait dans tous les languages de prog :p).

Voici le code:

[BITS 16]
[ORG 100h]

%define MAX_SIZE 80h; /!\ max:100h


start:
;lecture de la chaîne
mov cl,MAX_SIZE
lea bx,[palindrome]

call readline;bx => Récupération de la chaine & si => longueur

;Calcul de la moité de longueur
mov ax, si
mov cl, 2
div cl;al = quotient, ah = reste

;Lancement de la comparaison
mov di, 0

xor ch, ch
mov cl, al

.compa

mov dl, byte [bx + si - 1]
mov dh, byte [bx + di]
cmp dl, dh;Comparaison des deux 'bouts'
jne .notequ
inc di; Passage au prochaines car à comparer
dec si;


loop .compa

.equ:
mov ah, 09h
mov dx, bx
int 21h
mov dx, isequ
int 21h
ret

.notequ:
mov ah, 09h
mov dx, bx
int 21h
mov dx, isnequ
int 21h
ret

;Code pour la lecture d'une chaîne de car. 
;	entrées: bx = Endroit de la mémoire ou stocker le mot & cl = taille maximale du buffer (max 255)
;	retour: bx = Récupération de la chaine & si = longueur
readline:

mov ah, 0Ah;Pour la lecture (interruption 20h)
mov byte [bx], cl; Enregistrement de la taille du buffer max
mov dx, bx;Chaine sur dx (interruption 20h)
int 21h;lecture de la chaine de ds:dx (ah = Ah)

;Lecture de la taille du buffer trouvé 
xor cx, cx
mov cl, [byte bx+1];récupération de la taille tappée
mov si, cx;cx ==> pointeur
add bx, 2
mov byte [bx+si], '$';Ajout du '$' à la fin
retn

isequ db " est un palindrome", '$'
isnequ db " n'est pas un palindrome", '$'

palindrome resb MAX_SIZE

Je ne sais pas si vous voyez des choses à modifier afin d’améliorer le programme, du point de vue de l’optimisation par exemple.

Autre petite question, qui touche toujours à l’assembleur.
Sous NASM il n’existe pas la directive ASSUME. Es-ce handicapant sachant que MASM et TASM l’utilisent?

Pour ma part je ne vois pas grand chose à optimiser, d’un autre sens en assembleur optimiser revient souvent à réduire le nombre de ligne. Disons que j’aurais pris sensiblement le même algorithme.

Pour ce qui est de ta directive, je ne pourrais pas te dire car je n’ai fait que de l’assembleur sous linux avec as, gcc, etc… Les appels système ne sont pas les même non plus.

Bonjour,
je ne code pas en assembleur, mais des astuces que les autres me donnent:

  • pour diviser par 2 un décalage de bits suffit: c’est plus rapide que la division (surtout que tu ne te sert pas du reste)
  • mov di,0; est-ce qu’on a le droit de faire un xor di,di? (sinon oublie ma remarque)

Sinon, c’est assez rapide un algo de palindrome :neutre: j’imagine que c’est juste pour le fun :smiley:

Enfin, les int que tu utilise c’est du DOS non?
:slight_smile:

edit: et pour tester les perfs: boucle genre 1000 fois sur ton application, avec un mesure de temps au début et à la fin. teste avec différentes longueurs de palindromes.

oui en effet, d’après ce que j’ai vu pour mettre à zéro des registres ils utilisent ceci:

xor UN_REGISTRE, LE_MEME_REGISTRE

Il me semble que le xor est une instruction très rapide. C’est pour ça qu’ils utilisent j’imagine. A moins que ce ne soit juste pour frimer parce que plus difficile à comprendre :paf: (ou plutôt, moins intuitif)

Les interruptions que j’utilise sont bien celle du DOS. C’est vieux mais ça marche. D’ailleur, je sais pas si sous Vista il y aura encore un émulateur DOS, c’est tellement vieux :pt1cable:.
J’ai vu récemment qu’on pouvait appeler directement les routines du BIOS pour afficher les caractères (mais pas des chaînes directement). C’est la chose que je test actuellement.

Perso je fais de l’assembleur juste par intérêt, voir un peu comment ça fonctionne, mais je ne crois pas que mes connaissances en assembleur pourraient me servir dans le milieu professionnel. À moins qu’il existe encore une société qui produise des logiciels-fossiles mais j’en doute :paf: .

Et sinon, pour tester la rapidité du code, je vais essayer de faire comme tu l’as dit. Je vais faire fonctionner plusieurs fois le code et voir comment mon code réagit lorsque je modifie mes façons de faire.

A noter enfin, qu’il existe (d’après ce que j’ai lu sur mon livre d’ASM X86) des logiciels qui analysent le code ASM pour déterminer les gouleau d’étranglement et les modifier en conséquence.
Le logiciel cité est “VTune” de Intel que l’on peut télécharger en version démo à cette adresse: http://www.intel.com/software/products/vtune/vpa/eval.htm

Non, c’est pas uniquement de la frime (quoique pour pour initialiser un fois un compteur dans un programme, gagner 10 ns, c’est peut être un peu :MDR:)

Mais sinon, le but est de travailler essentiellement sur les registres et d’éviter les accès mémoire: quand tu fait un MOV, tu charge un contenu mémoire, alors qu’un XOR est uniquement sur des registres.
Idem avec les DIV et décalage de bits, avec en plus la division qui est je crois une des instruction les plus couteuses (enfin sutout en float): pour ça tu peux consulter le nombre de temps de cycle par instruction.

enfin il y a quelque astuces: par exemple pour échanger le contenu de 2 registres: plutôt que de faire 2 MOV, tu fait 3 XOR sur les registres… je cherche l’exemple si j’ai le temps :smiley:

:slight_smile:

http://en.wikipedia.org/wiki/XOR_swap ?? :sol:

J’ai en effet testé cette pratique. Mais selon plusieurs personnes sur le web cette technique est surtout faite pour les processeurs avec peu de registres (pas besoin de registre de swap).
Comme les 3 xor ne sont pas entrelacés (je sais pas si c’est le bon terme :pt1cable: , c’est quand une instructions attend celle “d’en dessus” avant de pouvoir être executée) le processeur n’aime pas (il peut pas exploiter ses pipelines). La technique avec le registre utilisé comme swap serait donc meilleur…

Et sinon, quand tu dis que mov n’est pas très bon parcqu’il traite de la mémoire, ceci s’applique quand je fais ? (ici je ne touche qu’au registre)

mov eax, ebx

Et quand je fais:

mov eax, 34h

Es-ce que je touche à la mémoire (sachant que le 34 est stocké en mémoire avec le programme).

EDIT: Je viens de trouver une personne qui disait ça sur internet :

et j’ai aussi trouvé ceci:

Donc il y a tout et son contraire…

>http://en.wikipedia.org/wiki/XOR_swap
c’est ça :super:
L’exécution entrelacée des instruction est une parallélisation des instructions.
Effectivement, si les instruction sont interdépendante, leur exécution ne peut pas être “entrelacée” elle reste donc séquentielle. Mais bon, si les 3 instructions sont plus rapides qu’une seule, ça reste plus rapide au final. à vérifier.
enfin cet algo peut quand même s’appliquer pour d’autres cas: sur des tableaux, si tu as déjà utilisé tous les registres…
autres “astuces”
http://graphics.stanford.edu/~seander/bithacks.html

>à partir du 80486, “mov ax,0” et “xor ax,ax” s’exécutent dans le
même temps.
Oui, les gars qui me donnaient ces conseils sont encore plus anciens que moi :smiley:
donc il est fort possible que depuis le 486, ce ne soit plus vrai.

> mov eax, ebx
C’est entre registre, donc je dirais pas d’accès mémoire.

>mov eax, 34h
là, il y a accès mémoire, si tu doit mettre cette valeur dans plusieurs registres, il faut penser à affecter les registres entre eux plutôt que depuis la mémoire…

Donc tout ça c’est la théorie, il faudrais voir avec des gens qui codent en assembleur pour la pratique :wink:

Il ne faut pas perdre de vue que pour optimiser pour un processeur, il faudrait prendre en compte tous les copro: FPU, extensions MMX, SSE1,2 et 3, 3DNow et EMT64 (prog SIMD entre autre). Moi l’ampleur de la tâche m’a toujours fait reculer :ane:

éventuellement un bout d’assembleur dans un C pour vraiment optimiser une partie cruciale.

:slight_smile:

Merci pour ces conseils. Je vais bien les enregistrer pour améliorer/optimiser mes futures applications.

Juste une dernière petite question. Je n’ai pas trouvé de récapitulatif du temps processeur utilisé par chacune des instructions. Es-ce que tu connaitrais une page qui donne pour chaque instruction un tableau récapitulatif?

EDIT: En parlant de temps processeur, je viens de découvrir le temps que prend l’instruction FCOS (cosinus) sur un 486: 257 cycles processeur !. Heureusement que le pipelining a été inventé, sinon en appelant quelques centaines de fois cette instruction on pourrait descendre en flèche les performance du dernier Core 2 Extrem de Intel :paf:

Détrompes-toi.
J’ai beaucoup pratiqué l’assembleur à la belle époque, une bonne technique d’optimisation consistant à dérouler les boucles le plus possible, ce qui produisait un nombre de lignes assez impressionnant malgré une exécution plus rapide.
Une autre technique consistait à inverser certaines lignes, pour optimiser l’accès au pipeline ou un truc dans le genre (c’est loin tout ça).

Sinon ma plus belle découverte (à l’époque où les graphiques étaient en 320x200), c’est que 320 = 256 + 64 :smiley:

Et il y a aussi la technique du prefetcher ou encore le forcage de l’utilisation du bus AGP pour faire des memcpy rapide comme l’éclaire :smiley:

galère à trouver: en anglais ça se dite clock cycle, et j’ai trouvé une référence jusqu’au 486. note qu’avec les proc modernes, ça dépend du core, donc c’est différent en AMD et intel, et ce sera différent par exemple entre 2 cores différents du P4 ou de l’A64.

http://www.cs.tut.fi/~siponen/upros/intel/index.html

Oui c’est vrai, on peut aussi s’amuser avec la fusion des boucles et faire en sorte qu’il y ait le moins d’accès mémoire possible sauf que là ca commence à être drôlement dépendant de l’architecture car il faut connaitre la taille du cache, comme il est géré lorsqu’il y a défaut de cache, etc… Dans un autre sens il est vrai que l’asm est quand même toujours très dépendant de l’architecture :stuck_out_tongue: