44 votes

Pourquoi le compilateur d'Intel préfère-t-il NEG + ADD à SUB?

En examinant les résultats de divers compilateurs pour une variété d'extraits de code, j'ai remarqué que le compilateur C d'Intel (CPI) a une forte tendance à préférer émettant une paire de NEG+ADD des instructions où d'autres compilateurs serait d'utiliser un seul SUB enseignement.

Comme simple exemple, considérez les points suivants du code C:

uint64_t Mod3(uint64_t value)
{
    return (value % 3);
}

La CPI se traduit par ce à la suite du code machine (indépendamment du niveau d'optimisation):

mov       rcx, 0xaaaaaaaaaaaaaaab
mov       rax, rdi
mul       rcx
shr       rdx, 1
lea       rsi, QWORD PTR [rdx+rdx*2]
neg       rsi                            ; \  equivalent to:
add       rdi, rsi                       ; /    sub  rdi, rsi
mov       rax, rdi
ret         

Alors que d'autres compilateurs (y compris MSVC, GCC et Clang) seront tous générer essentiellement code équivalent, sauf que l' NEG+ADD séquence est remplacée par un seul SUB enseignement.

Comme je l'ai dit, ce n'est pas juste un caprice de la façon dont la CPI compile ce code. C'est un modèle que j'ai observé à plusieurs reprises lors de l'analyse du démontage pour les opérations arithmétiques. Normalement, je ne pense pas que beaucoup de cela, sauf que la CPI est connu pour être un très bon compilateur optimisant et il est développé par des gens qui ont initié des informations sur leurs microprocesseurs.

Pourrait-il y avoir quelque chose que Intel sait à propos de la mise en œuvre de l' SUB instruction sur leurs processeurs qui le rend plus optimale afin de le décomposer en NEG+ADD instructions? À l'aide de RISC-style instructions qui décodent dans le plus simple µops est bien connu de l'optimisation des conseils pour moderne microarchitectures, ainsi est-il possible qu' SUB se décompose en interne en NEG et ADD µops, et qu'il est en fait plus efficace pour le front-end décodeur à l'utilisation de ces "simple" instructions? Les Processeurs modernes sont complexes, donc, tout est possible.

Agner le Brouillard de l'instruction complète des tables de confirmer mon intuition, cependant, que c'est en fait une pessimization. SUB est aussi efficace que ADD sur tous les processeurs, donc additionnelles exigées NEG instruction sert seulement ralentir les choses.

J'ai également couru les deux séquences par le biais d'Intel Architecture propre Analyseur de Code à analyser le débit. Mais le comptage de cycles et port liaisons varient d'une microarchitecture à l'autre, d'un seul SUB semble être supérieure dans tous les domaines de Nehalem à Broadwell. Voici les deux rapports générés par l'outil pour Haswell:

SOUS
Intel(R) Architecture Code Analyzer Version - 2.2 build:356c3b8 (Tue, 13 Dec 2016 16:25:20 +0200)
Binary Format - 64Bit
Architecture  - HSW
Analysis Type - Throughput

Throughput Analysis Report
--------------------------
Block Throughput: 1.85 Cycles       Throughput Bottleneck: Dependency chains (possibly between iterations)

Port Binding In Cycles Per Iteration:
---------------------------------------------------------------------------------------
|  Port  |  0   -  DV  |  1   |  2   -  D   |  3   -  D   |  4   |  5   |  6   |  7   |
---------------------------------------------------------------------------------------
| Cycles | 1.0    0.0  | 1.5  | 0.0    0.0  | 0.0    0.0  | 0.0  | 1.8  | 1.7  | 0.0  |
---------------------------------------------------------------------------------------

| Num Of |                    Ports pressure in cycles                     |    |
|  Uops  |  0  - DV  |  1  |  2  -  D  |  3  -  D  |  4  |  5  |  6  |  7  |    |
---------------------------------------------------------------------------------
|   1    | 0.1       | 0.2 |           |           |     | 0.3 | 0.4 |     | CP | mov rax, 0xaaaaaaaaaaaaaaab
|   2    |           | 1.0 |           |           |     |     | 1.0 |     | CP | mul rcx
|   1    | 0.9       |     |           |           |     |     | 0.1 |     | CP | shr rdx, 0x1
|   1    |           |     |           |           |     | 1.0 |     |     | CP | lea rax, ptr [rdx+rdx*2]
|   1    |           | 0.3 |           |           |     | 0.4 | 0.2 |     | CP | sub rcx, rax
|   1*   |           |     |           |           |     |     |     |     |    | mov rax, rcx
Total Num Of Uops: 7
NEG+AJOUTER
Intel(R) Architecture Code Analyzer Version - 2.2 build:356c3b8 (Tue, 13 Dec 2016 16:25:20 +0200)
Binary Format - 64Bit
Architecture  - HSW
Analysis Type - Throughput

Throughput Analysis Report
--------------------------
Block Throughput: 2.15 Cycles       Throughput Bottleneck: Dependency chains (possibly between iterations)

Port Binding In Cycles Per Iteration:
---------------------------------------------------------------------------------------
|  Port  |  0   -  DV  |  1   |  2   -  D   |  3   -  D   |  4   |  5   |  6   |  7   |
---------------------------------------------------------------------------------------
| Cycles | 1.1    0.0  | 2.0  | 0.0    0.0  | 0.0    0.0  | 0.0  | 2.0  | 2.0  | 0.0  |
---------------------------------------------------------------------------------------

| Num Of |                    Ports pressure in cycles                     |    |
|  Uops  |  0  - DV  |  1  |  2  -  D  |  3  -  D  |  4  |  5  |  6  |  7  |    |
---------------------------------------------------------------------------------
|   1    | 0.1       | 0.9 |           |           |     | 0.1 | 0.1 |     |    | mov rax, 0xaaaaaaaaaaaaaaab
|   2    |           | 1.0 |           |           |     |     | 1.0 |     | CP | mul rcx
|   1    | 1.0       |     |           |           |     |     |     |     | CP | shr rdx, 0x1
|   1    |           |     |           |           |     | 1.0 |     |     | CP | lea rax, ptr [rdx+rdx*2]
|   1    |           | 0.1 |           |           |     | 0.8 | 0.1 |     | CP | neg rax
|   1    | 0.1       |     |           |           |     | 0.1 | 0.9 |     | CP | add rcx, rax
|   1*   |           |     |           |           |     |     |     |     |    | mov rax, rcx
Total Num Of Uops: 8

Donc, autant que je peux dire, NEG+ADD augmente la taille du code, augmente le nombre de µops, augmente la pression pour l'exécution des ports, et augmente le nombre de cycles, ce qui résulte en une diminution nette du débit par rapport à l' SUB. Alors, pourquoi est-Intel compilateur de faire ceci?

Est-ce juste un caprice du générateur de code qui doivent être déclarés comme un défaut, ou alors j'ai loupé quelque mérite dans mon analyse?

2voto

kay27 Points 700

Étrangement, j'ai une réponse simple: Parce que la CPI n'est pas optimal.

Lorsque vous écrivez propre compilateur, vous obtenez commencé avec quelques très de base ensemble de codes d'opération: NOP, MOV, ADD... jusqu'à 10 opcodes. Vous n'utilisez pas l' SUB , pour un temps, car il pourrait facilement être remplacé par: ADD NEGgative operand. NEG n'est pas de base en tant que bien, comme il pourrait être remplacé par: XOR FFFF...; ADD 1.

Afin de vous mettre en œuvre plutôt complexe bits adressage des opérandes de types et de tailles. Vous le faites pour une seule machine code d'instruction (par exemple. ADD) et l'intention de l'utiliser plus loin pour la plupart des autres instructions. Mais à ce moment, votre co-travailleur finitions de la mise en œuvre optimale de calcul de reste, sans l'utilisation de SUB! Imaginez - c'est déjà appelé "Optimal_Mod" si vous ratez quelques inoptimal chose à l'intérieur non pas parce que vous êtes un mauvais gars et de la haine AMD mais juste parce que vous voyez il est déjà appelé optimale, optimisé.

Intel Compilateur est assez bonne en général, mais il a une longue version de l'histoire, de sorte qu'il peut se comportent bizarre dans certains cas rares. Je vous suggère de vous informer Intel sur cette question et d'envisager ce qui va se passer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X