Si vous multipliez deux valeurs que vous ne connaissez pas à l'avance, il est effectivement impossible de battre l'instruction de multiplication dans l'assembleur x86.
Si vous connaissez à l'avance la valeur de l'un des opérandes, vous pouvez battre l'instruction de multiplication en utilisant un petit nombre d'additions. Cela fonctionne particulièrement bien lorsque l'opérande connu est petit et n'a que quelques bits dans sa représentation binaire. Pour multiplier une valeur inconnue x par une valeur connue composée de 2^p + 2^q + ... 2^r il suffit d'ajouter x * 2^p + x * 2^q + .. x * 2 * r si bits p, q , ... et r sont définis. Ceci est facilement accompli en assembleur en déplaçant à gauche et en ajoutant:
; x in EDX
; product to EAX
xor eax,eax
shl edx,r ; x*2^r
add eax,edx
shl edx,q-r ; x*2^q
add eax,edx
shl edx,p-q ; x*2^p
add eax,edx
Le principal problème avec ceci est qu'il faut au moins 4 horloges pour le faire, en supposant une CPU superscalaire contrainte par les dépendances de registre.Multiplier prend généralement 10 ou moins horloges sur les processeurs modernes, et si cette séquence devient plus longue que dans le temps , vous pouvez aussi faire une multiplication.
multiplier par 9:
mov eax,edx ; same effect as xor eax,eax/shl edx 1/add eax,edx
shl edx,3 ; x*2^3
add eax,edx
Ce beats se multiplient; devrait seulement prendre 2 horloges. Ce qui est moins connu, c'est l'utilisation de l'instruction LEA (adresse effective de charge), pour obtenir une multiplication rapide par petite constante. LEA qui ne prend qu'une seule horloge dans le pire des cas son temps d'exécution peut souvent se chevaucher avec d'autres instructions par des CPU superscalaires.
LEA est essentiellement "ajouter deux valeurs avec de petits multiplicateurs constants". Il calcule t = 2^k * x + y pour k = 1,2,3 (voir le manuel de référence Intel) pour t, x et y étant n'importe quel registre. Si x == y, vous pouvez obtenir 1,2,3,4,5,8,9 fois x, mais en utilisant x et y comme registres séparés, vous pouvez combiner les résultats intermédiaires et vers d'autres registres (par exemple, à t), et cela s'avère remarquablement pratique. utilisant, vous pouvez réaliser une multiplication par 9 en utilisant une seule instruction:
lea eax,[edx*8+edx] ; takes 1 clock
En utilisant LEA avec soin, vous pouvez multiplier par une variété de constantes particulières dans un petit nombre de cycles:
lea eax,[edx*4+edx] ; 5 * edx
lea eax,[eax*2+edx] ; 11 * edx
lea eax,[eax*4] ; 44 * edx
Pour ce faire, vous devez décomposer votre multiplicateur constant en divers facteurs/sommes impliquant 1,2,3,4,5,8 et 9. Il est remarquable combien de petites constantes vous pouvez faire cela, et encore seulement utiliser 3-4 instructions.
Si vous autorisez l'utilisation d'autres instructions généralement une seule horloge (par exemple, SHL/SUB/GNA/MOV) vous pouvez multiplier par quelques valeurs constantes que LEA pur ne peut pas faire aussi efficacement par lui-même. Pour multiplier par 31:
lea eax,[4*edx]
lea eax,[8*eax] ; 32*edx
sub eax,edx; 31*edx ; 3 clocks
La séquence LEA correspondante est plus longue:
lea eax,[edx*4+edx]
lea eax,[edx*2+eax] ; eax*7
lea eax,[eax*2+edx] ; eax*15
lea eax,[eax*2+edx] ; eax*31 ; 4 clocks
Comprendre ces séquences est un peu difficile, mais vous pouvez mettre en place une attaque organisée. Puisque LEA, SHL, SOUS, NEG, MOV sont toutes les instructions d'une seule horloge pire cas, et zéro horloges si elles n'ont aucune dépendance à d'autres instructions, vous pouvez calculer le coût d'exeuction de toute une telle séquence. Cela signifie que vous pouvez implémenter un algorithme de programmation dynamique pour générer la meilleure séquence possible de telles instructions. Ceci n'est utile que si le nombre d'horloge est inférieur à la multiplication entière pour votre CPU (j'utilise 5 horloges comme règle de base), et il n'utilise pas tous les registres, ou au moins il doesn doesn n'utilisez pas les registres qui sont déjà occupés (évitez tout débordement).
J'ai effectivement construit ceci dans notre compilateur PARLANSE, et il est très efficace pour calculer des décalages dans des tableaux de structures A [i], où la taille de l'élément de structure dans A est la constante connue.Une personne intelligente mettra probablement la réponse en cache de sorte qu'elle ne doit pas être recalculée chaque fois que la même constante se produit; Je n'ai pas fait cela parce que le temps nécessaire pour générer de telles séquences est inférieur à ce que vous attendiez.
Il est légèrement intéressant d'imprimer les séquences d'instructions nécessaires pour multiplier par toutes les constantes de 1 à 10000. La plupart d'entre elles peuvent être faites dans 5-6 instructions le plus défavorable. Par conséquent, le compilateur PARLANSE utilise rarement une multiplication réelle lors de l'indexation même les tableaux les plus nuls de structures imbriquées.
pourquoi ubuntu dans les balises? –
@ x2 -Parce que je dois ajouter au moins cinq tags pour poster ma question, désolé. – Pavitar
quoi? Non, vous ne le faites pas. –