Le langage d'assemblage en ligne est-il plus lent que le code C ++ natif?

183

J'ai essayé de comparer les performances du langage d'assemblage en ligne et du code C ++, j'ai donc écrit une fonction qui ajoute deux tableaux de taille 2000 pour 100000 fois. Voici le code:

#define TIMES 100000
void calcuC(int *x,int *y,int length)
{
    for(int i = 0; i < TIMES; i++)
    {
        for(int j = 0; j < length; j++)
            x[j] += y[j];
    }
}


void calcuAsm(int *x,int *y,int lengthOfArray)
{
    __asm
    {
        mov edi,TIMES
        start:
        mov esi,0
        mov ecx,lengthOfArray
        label:
        mov edx,x
        push edx
        mov eax,DWORD PTR [edx + esi*4]
        mov edx,y
        mov ebx,DWORD PTR [edx + esi*4]
        add eax,ebx
        pop edx
        mov [edx + esi*4],eax
        inc esi
        loop label
        dec edi
        cmp edi,0
        jnz start
    };
}

Voici main():

int main() {
    bool errorOccured = false;
    setbuf(stdout,NULL);
    int *xC,*xAsm,*yC,*yAsm;
    xC = new int[2000];
    xAsm = new int[2000];
    yC = new int[2000];
    yAsm = new int[2000];
    for(int i = 0; i < 2000; i++)
    {
        xC[i] = 0;
        xAsm[i] = 0;
        yC[i] = i;
        yAsm[i] = i;
    }
    time_t start = clock();
    calcuC(xC,yC,2000);

    //    calcuAsm(xAsm,yAsm,2000);
    //    for(int i = 0; i < 2000; i++)
    //    {
    //        if(xC[i] != xAsm[i])
    //        {
    //            cout<<"xC["<<i<<"]="<<xC[i]<<" "<<"xAsm["<<i<<"]="<<xAsm[i]<<endl;
    //            errorOccured = true;
    //            break;
    //        }
    //    }
    //    if(errorOccured)
    //        cout<<"Error occurs!"<<endl;
    //    else
    //        cout<<"Works fine!"<<endl;

    time_t end = clock();

    //    cout<<"time = "<<(float)(end - start) / CLOCKS_PER_SEC<<"\n";

    cout<<"time = "<<end - start<<endl;
    return 0;
}

Ensuite, j'exécute le programme cinq fois pour obtenir les cycles de processeur, ce qui pourrait être considéré comme du temps. Chaque fois que j'appelle une des fonctions mentionnées ci-dessus uniquement.

Et voici le résultat.

Fonction de la version d'assemblage:

Debug   Release
---------------
732        668
733        680
659        672
667        675
684        694
Average:   677

Fonction de la version C ++:

Debug     Release
-----------------
1068      168
 999      166
1072      231
1002      166
1114      183
Average:  182

Le code C ++ en mode version est presque 3,7 fois plus rapide que le code d'assembly. Pourquoi?

Je suppose que le code d'assemblage que j'ai écrit n'est pas aussi efficace que ceux générés par GCC. Il est difficile pour un programmeur commun comme moi d'écrire du code plus rapidement que son adversaire généré par un compilateur, cela signifie-t-il que je ne devrais pas faire confiance aux performances du langage assembleur écrit par mes mains, me concentrer sur C ++ et oublier le langage assembleur?

user957121
la source
29
Plutôt. L'assemblage codé à la main est approprié dans certaines circonstances, mais il faut veiller à ce que la version d'assemblage soit effectivement plus rapide que ce qui peut être réalisé avec un langage de niveau supérieur.
Magnus Hoff
161
Vous trouverez peut-être instructif d'étudier le code généré par le compilateur et d'essayer de comprendre pourquoi il est plus rapide que votre version d'assembly.
Paul R
34
Ouais, on dirait que le compilateur est meilleur pour écrire asm que vous. Les compilateurs modernes sont vraiment très bons.
David Heffernan
20
Avez-vous regardé l'assemblage produit par GCC? Son éventuel GCC utilisait des instructions MMX. Votre fonction est très parallèle - vous pourriez potentiellement utiliser N processeurs pour calculer la somme en 1 / N ème du temps. Essayez une fonction où il n'y a aucun espoir de parallélisation.
Chris
11
Hm, je me serais attendu à ce qu'un bon compilateur fasse cela ~ 100000 fois plus vite ...
PlasmaHH

Réponses:

261

Oui, la plupart du temps.

Tout d'abord, vous partez de l'hypothèse erronée qu'un langage de bas niveau (assembly dans ce cas) produira toujours du code plus rapide qu'un langage de haut niveau (C ++ et C dans ce cas). Ce n'est pas vrai. Le code C est-il toujours plus rapide que le code Java? Non car il y a une autre variable: programmeur. La façon dont vous écrivez le code et la connaissance des détails de l'architecture influencent grandement les performances (comme vous l'avez vu dans ce cas).

Vous pouvez toujours produire un exemple où le code d'assemblage fait à la main est meilleur que le code compilé, mais il s'agit généralement d'un exemple fictif ou d'une seule routine pas un vrai programme de plus de 500 000 lignes de code C ++). Je pense que les compilateurs produiront un meilleur code d'assemblage 95% fois et parfois, seulement quelques rares fois, vous devrez peut-être écrire du code d'assemblage pour quelques routines courtes, très utilisées et critiques pour les performances ou lorsque vous devez accéder à des fonctionnalités de votre langage de haut niveau préféré n'expose pas. Voulez-vous une touche de cette complexité? Lisez cette réponse géniale ici sur SO.

Pourquoi ça?

Tout d'abord parce que les compilateurs peuvent faire des optimisations que nous ne pouvons même pas imaginer (voir cette courte liste ) et ils les feront en quelques secondes (lorsque nous aurons besoin de jours ).

Lorsque vous codez dans l'assembly, vous devez créer des fonctions bien définies avec une interface d'appel bien définie. Cependant, ils peuvent prendre en compte l'optimisation de l'ensemble du programme et l' optimisation inter-procédurale telles que l' allocation de registres , la propagation constante , l'élimination de sous-expressions communes , l' ordonnancement d'instructions et d'autres optimisations complexes et non évidentes ( modèle Polytope , par exemple). Sur l' architecture RISC , les gars ont cessé de s'inquiéter à ce sujet il y a de nombreuses années (la planification des instructions, par exemple, est très difficile à régler manuellement ) et les processeurs CISC modernes ont de très longs pipelines aussi.

Pour certains microcontrôleurs complexes, même les bibliothèques système sont écrites en C au lieu d'être assemblées car leurs compilateurs produisent un meilleur code final (et facile à entretenir).

Les compilateurs peuvent parfois utiliser automatiquement certaines instructions MMX / SIMDx par eux-mêmes, et si vous ne les utilisez pas, vous ne pouvez tout simplement pas comparer (d'autres réponses ont déjà très bien examiné votre code d'assemblage). Juste pour les boucles, voici une courte liste d'optimisations de boucle de ce qui est couramment vérifié par un compilateur (pensez-vous que vous pourriez le faire vous-même lorsque votre planning a été décidé pour un programme C #?) Si vous écrivez quelque chose en assembly, je pense que vous devez envisager au moins quelques optimisations simples . L'exemple de livre scolaire pour les tableaux consiste à dérouler le cycle (sa taille est connue au moment de la compilation). Faites-le et relancez votre test.

De nos jours, il est également très rare d'avoir besoin d'utiliser le langage d'assemblage pour une autre raison: la pléthore de processeurs différents . Voulez-vous tous les soutenir? Chacun a une microarchitecture spécifique et des jeux d'instructions spécifiques . Ils ont un nombre différent d'unités fonctionnelles et une notice de montage doivent être organisées pour les garder tous occupés . Si vous écrivez en C, vous pouvez utiliser PGO mais en assemblage, vous aurez alors besoin d'une grande connaissance de cette architecture spécifique (et repenser et tout refaire pour une autre architecture ). Pour les petites tâches du compilateur généralement fait mieux, et pour des tâches complexes habituellement le travail n'est pas remboursé (etcompilateur peut faire mieux de toute façon).

Si vous vous asseyez et que vous regardez votre code, vous verrez probablement que vous gagnerez plus à refondre votre algorithme qu'à traduire en assemblage (lisez cet excellent article ici sur SO ), il y a des optimisations de haut niveau (et conseils au compilateur) que vous pouvez appliquer efficacement avant de recourir au langage d'assemblage. Il vaut probablement la peine de mentionner que souvent, en utilisant des éléments intrinsèques, vous obtiendrez un gain de performances que vous recherchez et le compilateur pourra toujours effectuer la plupart de ses optimisations.

Cela dit, même si vous pouvez produire un code d'assemblage 5 à 10 fois plus rapide, vous devriez demander à vos clients s'ils préfèrent payer une semaine de votre temps ou acheter un processeur 50 $ plus rapide . L'optimisation extrême le plus souvent (et en particulier dans les applications LOB) n'est tout simplement pas requise de la plupart d'entre nous.

Adriano Repetti
la source
9
Bien sûr que non. Je pense que c'est mieux de 95% des gens dans 99% des cas. Parfois parce que c'est tout simplement trop coûteux (en raison de calculs complexes ) ou de temps (puis encore coûteux). Parfois parce que nous avons simplement oublié les optimisations ...
Adriano Repetti
62
@ ja72 - non, ce n'est pas meilleur pour écrire du code. C'est mieux pour optimiser le code.
Mike Baranczak
14
C'est contre-intuitif jusqu'à ce que vous y réfléchissiez vraiment. De la même manière, les machines basées sur les VM commencent à effectuer des optimisations d'exécution que les compilateurs n'ont tout simplement pas les informations à effectuer.
Bill K
6
@ M28: les compilateurs peuvent utiliser les mêmes instructions. Bien sûr, ils le paient en termes de taille binaire (car ils doivent fournir un chemin de secours au cas où ces instructions ne seraient pas prises en charge). En outre, pour la plupart, les "nouvelles instructions" qui seraient ajoutées sont de toute façon des instructions SMID, que les VM et les compilateurs sont assez horribles à utiliser. Les machines virtuelles paient pour cette fonctionnalité en ce sens qu'elles doivent compiler le code au démarrage.
Billy ONeal
9
@BillK: PGO fait la même chose pour les compilateurs.
Billy ONeal
194

Votre code d'assemblage est sous-optimal et peut être amélioré:

  • Vous poussez et sautez un registre ( EDX ) dans votre boucle intérieure. Cela devrait être retiré de la boucle.
  • Vous rechargez les pointeurs de tableau à chaque itération de la boucle. Cela devrait sortir de la boucle.
  • Vous utilisez l' loopinstruction, qui est connue pour être extrêmement lente sur la plupart des processeurs modernes (probablement en raison de l'utilisation d'un ancien livre d'assemblage *)
  • Vous ne tirez aucun avantage du déroulement manuel des boucles.
  • Vous n'utilisez pas les instructions SIMD disponibles .

Donc, à moins que vous n'amélioriez considérablement vos compétences en assembleur, cela n'a pas de sens pour vous d'écrire du code assembleur pour les performances.

* Bien sûr, je ne sais pas si vous avez vraiment reçu les loopinstructions d'un ancien livre de montage. Mais vous ne le voyez presque jamais dans le code du monde réel, car chaque compilateur est assez intelligent pour ne pas émettre loop, vous ne le voyez que dans des livres mauvais et obsolètes à mon humble avis.

Gunther Piez
la source
les compilateurs peuvent toujours émettre loop(et de nombreuses instructions "obsolètes") si vous optimisez pour la taille
phuclv
1
@phuclv eh bien oui, mais la question originale concernait exactement la vitesse, pas la taille.
IGR94
60

Même avant de plonger dans l'assemblage, il existe des transformations de code qui existent à un niveau supérieur.

static int const TIMES = 100000;

void calcuC(int *x, int *y, int length) {
  for (int i = 0; i < TIMES; i++) {
    for (int j = 0; j < length; j++) {
      x[j] += y[j];
    }
  }
}

peut être transformé en via Loop Rotation :

static int const TIMES = 100000;

void calcuC(int *x, int *y, int length) {
    for (int j = 0; j < length; ++j) {
      for (int i = 0; i < TIMES; ++i) {
        x[j] += y[j];
      }
    }
}

ce qui est bien meilleur en ce qui concerne la localité mémoire.

Cela pourrait être optimisé davantage, faire a += bX fois équivaut à le faire a += X * b, nous obtenons:

static int const TIMES = 100000;

void calcuC(int *x, int *y, int length) {
    for (int j = 0; j < length; ++j) {
      x[j] += TIMES * y[j];
    }
}

cependant, il semble que mon optimiseur préféré (LLVM) n'effectue pas cette transformation.

[modifier] J'ai trouvé que la transformation est effectuée si nous avions le restrictqualificatif à xet y. En effet sans cette restriction, x[j]et y[j]pourrait alias au même emplacement ce qui rend cette transformation erronée. [fin de la modification]

, En tout cas c'est, je pense, la version optimisée C. C'est déjà beaucoup plus simple. Sur cette base, voici ma fissure chez ASM (je laisse Clang la générer, je suis inutile):

calcuAsm:                               # @calcuAsm
.Ltmp0:
    .cfi_startproc
# BB#0:
    testl   %edx, %edx
    jle .LBB0_2
    .align  16, 0x90
.LBB0_1:                                # %.lr.ph
                                        # =>This Inner Loop Header: Depth=1
    imull   $100000, (%rsi), %eax   # imm = 0x186A0
    addl    %eax, (%rdi)
    addq    $4, %rsi
    addq    $4, %rdi
    decl    %edx
    jne .LBB0_1
.LBB0_2:                                # %._crit_edge
    ret
.Ltmp1:
    .size   calcuAsm, .Ltmp1-calcuAsm
.Ltmp2:
    .cfi_endproc

J'ai peur de ne pas comprendre d'où viennent toutes ces instructions, mais vous pouvez toujours vous amuser et essayer de voir comment cela se compare ... mais j'utiliserais toujours la version optimisée C plutôt que l'assemblage, dans le code, beaucoup plus portable.

Matthieu M.
la source
Merci pour votre réponse. Eh bien, c'est un peu déroutant que lorsque j'ai pris la classe nommée "Principes du compilateur", j'ai appris que le compilateur optimisera notre code par de nombreux moyens. Cela signifie-t-il que nous devons optimiser notre code manuellement? Pouvons-nous faire un meilleur travail que le compilateur? C'est la question qui me déroute toujours.
user957121
2
@ user957121: nous pouvons mieux l'optimiser lorsque nous avons plus d'informations. Plus précisément, ici, ce qui gêne le compilateur est le possible aliasing entre xet y. Autrement dit, le compilateur ne peut pas être sûr que pour tous i,jen [0, length)nous avons x + i != y + j. S'il y a chevauchement, l'optimisation est impossible. Le langage C a introduit le restrictmot - clé pour indiquer au compilateur que deux pointeurs ne peuvent pas d'alias, mais cela ne fonctionne pas pour les tableaux car ils peuvent toujours se chevaucher même s'ils ne sont pas exactement d'alias.
Matthieu M.
La vectorisation automatique actuelle de GCC et Clang (après avoir vérifié l'absence de chevauchement si vous l'omettez __restrict). SSE2 est la référence pour x86-64, et avec la lecture aléatoire, SSE2 peut faire 2x multiplications 32 bits à la fois (produisant des produits 64 bits, d'où le brassage pour rassembler les résultats). godbolt.org/z/r7F_uo . (SSE4.1 est nécessaire pour pmulld: 32x32 compacté => multiplication 32 bits). GCC a une astuce intéressante pour transformer des multiplicateurs entiers constants en décalage / addition (et / ou soustraction), ce qui est bon pour les multiplicateurs avec peu de bits définis. Le code de lecture aléatoire de Clang va entraîner un goulot d'étranglement sur le débit de lecture aléatoire sur les processeurs Intel.
Peter Cordes
41

Réponse courte: oui.

Réponse longue: oui, à moins que vous ne sachiez vraiment ce que vous faites et que vous ayez une raison de le faire.

Oliver Charlesworth
la source
3
et seulement si vous avez exécuté un outil de profilage au niveau de l'assemblage comme vtune pour les puces Intel pour voir où vous pourrez peut-être améliorer les choses
Mark Mullin
1
Cela répond techniquement à la question mais est également totalement inutile. Un -1 de ma part.
Navin
2
Réponse très longue: "Oui, sauf si vous avez envie de changer tout votre code chaque fois qu'un nouveau (er) processeur est utilisé. Choisissez le meilleur algorithme, mais laissez le compilateur faire l'optimisation"
Tommylee2k
35

J'ai corrigé mon code asm:

  __asm
{   
    mov ebx,TIMES
 start:
    mov ecx,lengthOfArray
    mov esi,x
    shr ecx,1
    mov edi,y
label:
    movq mm0,QWORD PTR[esi]
    paddd mm0,QWORD PTR[edi]
    add edi,8
    movq QWORD PTR[esi],mm0
    add esi,8
    dec ecx 
    jnz label
    dec ebx
    jnz start
};

Résultats pour la version Release:

 Function of assembly version: 81
 Function of C++ version: 161

Le code d'assemblage en mode version est presque 2 fois plus rapide que le C ++.

sasha
la source
18
Maintenant, si vous commencez à utiliser SSE au lieu de MMX (le nom du registre est à la xmm0place de mm0), vous obtiendrez une autre accélération d'un facteur deux ;-)
Gunther Piez
8
J'ai changé, j'ai eu 41 pour la version d'assemblage. C'est en 4 fois plus rapide :)
sasha
3
peut également obtenir jusqu'à 5% de plus si vous utilisez tous les registres
xmm
7
Maintenant, si vous pensez au temps que cela vous a réellement pris: le montage, environ 10 heures environ? C ++, quelques minutes je suppose? Il y a un gagnant clair ici, à moins qu'il ne s'agisse d'un code critique pour les performances.
Calimo
1
Un bon compilateur va déjà vectoriser automatiquement avec paddd xmm(après avoir vérifié le chevauchement entre xet y, car vous ne l'avez pas utilisé int *__restrict x). Par exemple, gcc fait cela: godbolt.org/z/c2JG0- . Ou après s'être inséré dans main, il ne devrait pas avoir besoin de vérifier le chevauchement car il peut voir l'allocation et prouver qu'ils ne se chevauchent pas. (Et cela supposerait également un alignement de 16 octets sur certaines implémentations x86-64, ce qui n'est pas le cas pour la définition autonome.) Et si vous compilez avec gcc -O3 -march=native, vous pouvez obtenir 256 bits ou 512 bits vectorisation.
Peter Cordes
24

Cela signifie-t-il que je ne devrais pas faire confiance aux performances du langage d'assemblage écrit par mes mains

Oui, c'est exactement ce que cela signifie, et c'est vrai pour toutes les langues. Si vous ne savez pas comment écrire du code efficace dans le langage X, vous ne devriez pas faire confiance à votre capacité à écrire du code efficace en X. Et donc, si vous voulez du code efficace, vous devriez utiliser un autre langage.

L'assemblage est particulièrement sensible à cela, car, eh bien, ce que vous voyez est ce que vous obtenez. Vous écrivez les instructions spécifiques que vous souhaitez que la CPU exécute. Avec les langages de haut niveau, il existe un compilateur entre deux, qui peut transformer votre code et supprimer de nombreuses inefficacités. Avec l'assemblage, vous êtes seul.

jalf
la source
2
Je pense que c'est pour écrire que, en particulier pour un processeur x86 moderne, il est exceptionnellement difficile d'écrire un code d'assemblage efficace en raison de la présence de pipelines, de plusieurs unités d'exécution et d'autres gadgets à l'intérieur de chaque noyau. L'écriture de code qui équilibre l'utilisation de toutes ces ressources afin d'obtenir la vitesse d'exécution la plus élevée aboutira souvent à un code avec une logique non directe qui «ne devrait pas» être rapide selon la sagesse d'assemblage «conventionnelle». Mais pour les processeurs moins complexes, d'après mon expérience, la génération de code du compilateur C peut être considérablement améliorée.
Olof Forshell
4
Le code des compilateurs C peut généralement être amélioré, même sur un processeur x86 moderne. Mais vous devez bien comprendre le processeur, ce qui est plus difficile à faire avec un processeur x86 moderne. C'est mon point. Si vous ne comprenez pas le matériel que vous ciblez, vous ne pourrez pas l'optimiser. Et puis le compilateur fera probablement un meilleur travail
jalf
1
Et si vous voulez vraiment faire sauter le compilateur, vous devez être créatif et optimiser d'une manière que le compilateur ne peut pas. C'est un compromis temps / récompense, c'est pourquoi C est un langage de script pour certains et un code intermédiaire pour un langage de niveau supérieur pour d'autres. Pour moi cependant, l'assemblage est plus pour le plaisir :). beaucoup comme grc.com/smgassembly.htm
Hawken
22

La seule raison d'utiliser le langage assembleur de nos jours est d'utiliser certaines fonctionnalités non accessibles par le langage.

Ceci s'applique à:

  • Programmation du noyau qui a besoin d'accéder à certaines fonctionnalités matérielles telles que la MMU
  • Programmation haute performance qui utilise des instructions vectorielles ou multimédias très spécifiques non prises en charge par votre compilateur.

Mais les compilateurs actuels sont assez intelligents, ils peuvent même remplacer deux instructions séparées comme d = a / b; r = a % b;par une seule instruction qui calcule la division et le reste en une seule fois si elle est disponible, même si C n'a pas un tel opérateur.

fortran
la source
10
Il y a d'autres endroits pour l'ASM en plus de ces deux-là. À savoir, une bibliothèque bignum sera généralement beaucoup plus rapide dans ASM que C, en raison du fait d'avoir accès aux drapeaux et à la partie supérieure de la multiplication, etc. Vous pouvez également faire ces choses dans le portable C, mais elles sont très lentes.
Mooing Duck
@MooingDuck Cela pourrait être considéré comme un accès à des fonctionnalités matérielles qui ne sont pas directement disponibles dans le langage ... Mais tant que vous traduisez simplement votre code de haut niveau en assemblage à la main, le compilateur vous battra.
fortran
1
c'est ça, mais ce n'est pas de la programmation du noyau, ni spécifique au fournisseur. Bien qu'avec de légers changements de fonctionnement, il pourrait facilement tomber dans l'une ou l'autre catégorie. Id devinez ASM lorsque vous voulez les performances des instructions de processeur qui n'ont pas de mappage C.
Mooing Duck
1
@fortran Vous dites simplement que si vous n'optimisez pas votre code, ce ne sera pas aussi rapide que le code optimisé par le compilateur. L'optimisation est la raison pour laquelle on écrirait l'assemblage en premier lieu. Si vous voulez dire traduire puis optimiser, il n'y a aucune raison pour que le compilateur vous batte à moins que vous ne soyez pas doué pour optimiser l'assemblage. Donc, pour battre le compilateur, vous devez optimiser d'une manière que le compilateur ne peut pas. C'est assez explicite. La seule raison d'écrire l'assembly est si vous êtes meilleur qu'un compilateur / interpréteur . Cela a toujours été la raison pratique d'écrire l'assemblage.
Hawken
1
Dire simplement: Clang a accès aux indicateurs de retenue, à la multiplication de 128 bits et ainsi de suite grâce aux fonctions intégrées. Et il peut intégrer tout cela dans ses algorithmes d'optimisation normaux.
gnasher729
19

Il est vrai qu'un compilateur moderne fait un travail incroyable dans l'optimisation du code, mais je vous encourage tout de même à continuer à apprendre l'assemblage.

Tout d'abord, vous n'êtes clairement pas intimidé par cela , c'est un grand, un grand plus, ensuite - vous êtes sur la bonne voie en profilant afin de valider ou de rejeter vos hypothèses de vitesse , vous demandez l' avis de personnes expérimentées , et vous ont le plus grand outil d'optimisation connu de l'humanité: un cerveau .

Au fur et à mesure que votre expérience augmente, vous apprendrez quand et où l'utiliser (généralement les boucles les plus étroites et les plus internes de votre code, une fois que vous l'avez profondément optimisée au niveau algorithmique).

Pour vous inspirer, je vous recommande de consulter les articles de Michael Abrash (si vous n'avez pas entendu parler de lui, c'est un gourou de l'optimisation; il a même collaboré avec John Carmack dans l'optimisation du logiciel de rendu Quake!)

"le code le plus rapide n'existe pas" - Michael Abrash


la source
2
Je crois qu'un des livres de Michael Abrash est le livre noir de programmation graphique. Mais il n'est pas le seul à utiliser l'assemblage, Chris Sawyer a écrit les deux premiers jeux de magnat des montagnes russes en assemblage par lui-même.
Hawken
14

J'ai changé le code asm:

 __asm
{ 
    mov ebx,TIMES
 start:
    mov ecx,lengthOfArray
    mov esi,x
    shr ecx,2
    mov edi,y
label:
    mov eax,DWORD PTR [esi]
    add eax,DWORD PTR [edi]
    add edi,4   
    dec ecx 
    mov DWORD PTR [esi],eax
    add esi,4
    test ecx,ecx
    jnz label
    dec ebx
    test ebx,ebx
    jnz start
};

Résultats pour la version Release:

 Function of assembly version: 41
 Function of C++ version: 161

Le code d'assemblage en mode version est presque 4 fois plus rapide que le C ++. IMHo, la vitesse du code d'assemblage dépend du programmeur

sasha
la source
Oui, mon code a vraiment besoin d'être optimisé, bon travail pour vous et merci!
user957121
5
C'est quatre fois plus rapide car vous ne faites qu'un quart du travail :-) Le shr ecx,2est superflu, car la longueur du tableau est déjà donnée en intet non en octet. Vous atteignez donc fondamentalement la même vitesse. Vous pouvez essayer la padddréponse de harolds, ce sera vraiment plus rapide.
Gunther Piez
13

c'est un sujet très intéressant!
J'ai changé le MMX par SSE dans le code de Sasha
Voici mes résultats:

Function of C++ version:      315
Function of assembly(simply): 312
Function of assembly  (MMX):  136
Function of assembly  (SSE):  62

Le code d'assemblage avec SSE est 5 fois plus rapide que le C ++

salaoshi
la source
12

La plupart des compilateurs de langages de haut niveau sont très optimisés et savent ce qu'ils font. Vous pouvez essayer de vider le code de désassemblage et le comparer avec votre assembly natif. Je crois que vous verrez quelques astuces intéressantes que votre compilateur utilise.

Juste par exemple, même si je ne suis plus sûr que ce soit juste :):

Faire:

mov eax,0

coûte plus de cycles que

xor eax,eax

qui fait la même chose.

Le compilateur connaît toutes ces astuces et les utilise.

Nuno_147
la source
4
Toujours vrai, voir stackoverflow.com/questions/1396527/… . Pas à cause des cycles utilisés, mais à cause de l'encombrement réduit de la mémoire.
Gunther Piez
10

Le compilateur vous a battu. Je vais essayer, mais je ne donnerai aucune garantie. Je suppose que la "multiplication" par TIMES est censée en faire un test de performance plus pertinent, qui yet xsont alignés sur 16, et c'est lengthun multiple non nul de 4. C'est probablement tout vrai de toute façon.

  mov ecx,length
  lea esi,[y+4*ecx]
  lea edi,[x+4*ecx]
  neg ecx
loop:
  movdqa xmm0,[esi+4*ecx]
  paddd xmm0,[edi+4*ecx]
  movdqa [edi+4*ecx],xmm0
  add ecx,4
  jnz loop

Comme je l'ai dit, je ne donne aucune garantie. Mais je serai surpris si cela peut être fait beaucoup plus rapidement - le goulot d'étranglement ici est le débit mémoire même si tout est un hit L1.

Harold
la source
Je pense que l'adressage complexe ralentit votre code, si vous changez le code en mov ecx, length, lea ecx,[ecx*4], mov eax,16... add ecx,eaxpuis utilisez simplement [esi + ecx] partout, vous éviterez 1 cycle de décrochage par instruction, accélérant les lots de boucles. (Si vous avez le dernier Skylake, cela ne s'applique pas). L'ajout reg, reg rend juste la boucle plus serrée, ce qui peut ou non aider.
Johan
@Johan ça ne devrait pas être un blocage, juste une latence de cycle supplémentaire, mais bien sûr que ça ne peut pas faire de mal de ne pas l'avoir .. J'ai écrit ce code pour Core2 qui n'avait pas ce problème. Est-ce que r + r n'est pas aussi "complexe" btw?
harold
7

L'implémentation aveugle du même algorithme, instruction par instruction, dans l'assemblage est garantie d'être plus lente que ce que le compilateur peut faire.

C'est parce que même la plus petite optimisation faite par le compilateur est meilleure que votre code rigide sans aucune optimisation.

Bien sûr, il est possible de battre le compilateur, surtout s'il s'agit d'une petite partie localisée du code, j'ai même dû le faire moi-même pour obtenir un env. Vitesse 4x, mais dans ce cas, nous devons fortement nous fier à une bonne connaissance du matériel et à de nombreuses astuces apparemment contre-intuitives.

vsz
la source
3
Je pense que cela dépend du langage et du compilateur. Je peux imaginer un compilateur C extrêmement inefficace dont la sortie pourrait facilement être battue par un assemblage simple d'écriture humaine. Le CCG, pas tellement.
Casey Rodarmor
Les compilateurs C / ++ étant une telle entreprise, et seulement 3 grands autour, ils ont tendance à être plutôt bons dans ce qu'ils font. Il est encore (très) possible dans certaines circonstances que l'assemblage manuscrit soit plus rapide; beaucoup de bibliothèques mathématiques passent à asm pour mieux gérer les valeurs multiples / larges. Donc, bien que la garantie soit un peu trop forte, c'est probable.
ssube
@peachykeen: Je ne voulais pas dire que l'assemblage est garanti plus lent que C ++ en général. Je voulais dire cette "garantie" dans le cas où vous avez un code C ++ et le traduisez aveuglément ligne par ligne en assemblage. Lisez aussi le dernier paragraphe de ma réponse :)
vsz
5

En tant que compilateur, je remplacerais une boucle de taille fixe pour de nombreuses tâches d'exécution.

int a = 10;
for (int i = 0; i < 3; i += 1) {
    a = a + i;
}

produira

int a = 10;
a = a + 0;
a = a + 1;
a = a + 2;

et finalement il saura que "a = a + 0;" est inutile donc il supprimera cette ligne. Espérons que quelque chose dans votre tête est maintenant prêt à joindre des options d'optimisation en commentaire. Toutes ces optimisations très efficaces rendront le langage compilé plus rapide.

Miah
la source
4
Et à moins que ce ne asoit volatile, il y a de fortes chances que le compilateur fasse juste int a = 13;depuis le tout début.
vsz
4

C'est exactement ce que cela signifie. Laissez les micro-optimisations au compilateur.

Luchian Grigore
la source
4

J'adore cet exemple car il illustre une leçon importante sur le code de bas niveau. Oui, vous pouvez écrire un assembly aussi rapide que votre code C. C'est tautologiquement vrai, mais cela ne veut pas nécessairement dire quoi que ce soit. Il est clair que quelqu'un le peut, sinon l'assembleur ne connaîtrait pas les optimisations appropriées.

De même, le même principe s'applique lorsque vous montez dans la hiérarchie de l'abstraction du langage. Oui, vous pouvez écrire un analyseur en C aussi rapide qu'un script perl rapide et sale, et beaucoup de gens le font. Mais cela ne veut pas dire que parce que vous avez utilisé C, votre code sera rapide. Dans de nombreux cas, les langages de niveau supérieur effectuent des optimisations que vous n'avez peut-être même jamais envisagées.

Tylerl
la source
3

Dans de nombreux cas, la manière optimale d'exécuter une tâche peut dépendre du contexte dans lequel la tâche est exécutée. Si une routine est écrite en langage d'assemblage, il ne sera généralement pas possible de faire varier la séquence d'instructions en fonction du contexte. À titre d'exemple simple, considérons la méthode simple suivante:

inline void set_port_high(void)
{
  (*((volatile unsigned char*)0x40001204) = 0xFF);
}

Un compilateur pour le code ARM 32 bits, étant donné ce qui précède, le rendrait probablement comme quelque chose comme:

ldr  r0,=0x40001204
mov  r1,#0
strb r1,[r0]
[a fourth word somewhere holding the constant 0x40001204]

ou peut-être

ldr  r0,=0x40001000  ; Some assemblers like to round pointer loads to multiples of 4096
mov  r1,#0
strb r1,[r0+0x204]
[a fourth word somewhere holding the constant 0x40001000]

Cela pourrait être légèrement optimisé dans un code assemblé à la main, comme:

ldr  r0,=0x400011FF
strb r0,[r0+5]
[a third word somewhere holding the constant 0x400011FF]

ou

mvn  r0,#0xC0       ; Load with 0x3FFFFFFF
add  r0,r0,#0x1200  ; Add 0x1200, yielding 0x400011FF
strb r0,[r0+5]

Les deux approches assemblées manuellement nécessiteraient 12 octets d'espace de code au lieu de 16; ce dernier remplacerait un "load" par un "add", qui sur un ARM7-TDMI exécuterait deux cycles plus rapidement. Si le code devait être exécuté dans un contexte où r0 était ne sait pas / ne se soucie pas, les versions en langage assembleur seraient donc un peu meilleures que la version compilée. D'un autre côté, supposons que le compilateur sache qu'un certain registre [par exemple r5] allait contenir une valeur qui était à moins de 2047 octets de l'adresse désirée 0x40001204 [par exemple 0x40001000], et savait en outre qu'un autre registre [par exemple r7] allait pour contenir une valeur dont les bits faibles étaient 0xFF. Dans ce cas, un compilateur pourrait optimiser la version C du code pour simplement:

strb r7,[r5+0x204]

Beaucoup plus court et plus rapide que même le code d'assemblage optimisé à la main. De plus, supposons que set_port_high se soit produit dans le contexte:

int temp = function1();
set_port_high();
function2(temp); // Assume temp is not used after this

Pas du tout invraisemblable lors du codage pour un système embarqué. Si set_port_highest écrit en code d'assemblage, le compilateur devrait déplacer r0 (qui contient la valeur de retour de function1) ailleurs avant d'appeler le code d'assemblage, puis déplacer cette valeur vers r0 par la suite (car function2attendra son premier paramètre dans r0), le code d'assemblage "optimisé" aurait donc besoin de cinq instructions. Même si le compilateur ne connaissait aucun registre contenant l'adresse ou la valeur à stocker, sa version à quatre instructions (qu'il pourrait adapter pour utiliser tous les registres disponibles - pas nécessairement r0 et r1) battrait l'assemblage "optimisé" -version linguistique. Si le compilateur avait l'adresse et les données nécessaires dans r5 et r7 comme décrit précédemment, instruction -function1 ne modifierait pas ces registres et pourrait donc remplacerset_port_highavec quatre instructions plus petites et plus rapides que le code d'assemblage «optimisé à la main».strb

Notez que le code d'assemblage optimisé à la main peut souvent surpasser un compilateur dans les cas où le programmeur connaît le déroulement précis du programme, mais les compilateurs brillent dans les cas où un morceau de code est écrit avant que son contexte ne soit connu, ou où un morceau de code source peut être invoqué à partir de plusieurs contextes [s'il set_port_highest utilisé à cinquante endroits différents dans le code, le compilateur pourrait décider indépendamment pour chacun de ceux-ci de la meilleure façon de l'étendre].

En général, je suggérerais que le langage assembleur est susceptible d'apporter les plus grandes améliorations de performances dans les cas où chaque morceau de code peut être approché à partir d'un nombre très limité de contextes, et est susceptible d'être préjudiciable aux performances dans les endroits où un morceau de le code peut être abordé dans de nombreux contextes différents. Fait intéressant (et pratique), les cas où l'assemblage est le plus bénéfique pour les performances sont souvent ceux où le code est le plus simple et le plus facile à lire. Les endroits où le code en langage assembleur se transformerait en un gâchis gluant sont souvent ceux où l'écriture en assembleur offrirait le plus petit avantage en termes de performances.

[Note mineure: il y a des endroits où le code d'assemblage peut être utilisé pour produire un désordre gluant hyper-optimisé; par exemple, un morceau de code que j'ai créé pour l'ARM avait besoin de récupérer un mot de la RAM et d'exécuter l'une des douze routines environ basées sur les six bits supérieurs de la valeur (de nombreuses valeurs mappées sur la même routine). Je pense que j'ai optimisé ce code pour quelque chose comme:

ldrh  r0,[r1],#2! ; Fetch with post-increment
ldrb  r1,[r8,r0 asr #10]
sub   pc,r8,r1,asl #2

Le registre r8 contenait toujours l'adresse de la table de répartition principale (dans la boucle où le code passe 98% de son temps, rien ne l'a jamais utilisé à d'autres fins); les 64 entrées se référaient à des adresses dans les 256 octets qui la précédaient. Étant donné que la boucle primaire avait dans la plupart des cas une limite de temps d'exécution stricte d'environ 60 cycles, la récupération et l'envoi de neuf cycles ont été très utiles pour atteindre cet objectif. Utiliser une table de 256 adresses 32 bits aurait été un cycle plus rapide, mais aurait englouti 1 Ko de RAM très précieuse [le flash aurait ajouté plus d'un état d'attente]. Utiliser 64 adresses 32 bits aurait nécessité l'ajout d'une instruction pour masquer certains bits du mot récupéré, et aurait encore englouti 192 octets de plus que la table que j'ai réellement utilisée. L'utilisation du tableau des décalages 8 bits a donné un code très compact et rapide, mais ce n'est pas quelque chose que j'attendrais d'un compilateur; Je ne m'attendrais pas non plus à ce qu'un compilateur consacre un registre «à plein temps» à la tenue de l'adresse de la table.

Le code ci-dessus a été conçu pour fonctionner comme un système autonome; il pouvait périodiquement appeler du code C, mais seulement à certains moments où le matériel avec lequel il communiquait pouvait être mis en toute sécurité dans un état «inactif» pendant deux intervalles d'environ une milliseconde toutes les 16 ms.

supercat
la source
2

Ces derniers temps, toutes les optimisations de vitesse que j'ai faites remplaçaient le code lent endommagé par le cerveau par du code juste raisonnable. Mais pour les choses où la vitesse était vraiment critique et que je faisais de gros efforts pour faire quelque chose rapidement, le résultat était toujours un processus itératif, où chaque itération donnait plus d'informations sur le problème, trouvant des moyens de résoudre le problème avec moins d'opérations. La vitesse finale dépendait toujours de la compréhension que j'avais du problème. Si à un moment donné j'avais utilisé du code d'assemblage ou du code C suroptimisé, le processus de recherche d'une meilleure solution aurait souffert et le résultat final serait plus lent.

gnasher729
la source
2

C ++ est plus rapide à moins que vous n'utilisiez le langage assembleur avec des connaissances plus approfondies de la manière correcte.

Lorsque je code dans ASM, je réorganise les instructions manuellement afin que le CPU puisse en exécuter davantage en parallèle lorsque cela est logiquement possible. J'utilise à peine la RAM lorsque je code en ASM, par exemple: il pourrait y avoir plus de 20000 lignes de code dans ASM et je n'ai jamais utilisé push / pop.

Vous pourriez potentiellement sauter au milieu de l'opcode pour auto-modifier le code et le comportement sans la pénalité possible d'un code auto-modifiable. L'accès aux registres prend 1 tick (prend parfois 0,25 ticks) du CPU. L'accès à la RAM peut prendre des centaines.

Pour ma dernière aventure ASM, je n'ai jamais utilisé la RAM pour stocker une variable (pour des milliers de lignes d'ASM). ASM pourrait être potentiellement inimaginablement plus rapide que C ++. Mais cela dépend de nombreux facteurs variables tels que:

1. I was writing my apps to run on the bare metal.
2. I was writing my own boot loader that was starting my programs in ASM so there was no OS management in the middle.

J'apprends maintenant C # et C ++ parce que j'ai réalisé que la productivité est importante !! Vous pouvez essayer de faire les programmes les plus rapides imaginables en utilisant l'ASM pur seul pendant le temps libre. Mais pour produire quelque chose, utilisez un langage de haut niveau.

Par exemple, le dernier programme que j'ai codé utilisait JS et GLSL et je n'ai jamais remarqué de problème de performance, même en parlant de JS qui est lent. En effet, le simple concept de programmation du GPU pour la 3D rend la vitesse du langage qui envoie les commandes au GPU presque sans importance.

La rapidité de l'assembleur seul sur le métal nu est irréfutable. Cela pourrait-il être encore plus lent en C ++? - Cela peut être dû au fait que vous écrivez du code d'assembly avec un compilateur n'utilisant pas d'assembleur pour commencer.

Mon conseil personnel est de ne jamais écrire de code d'assemblage si vous pouvez l'éviter, même si j'aime l'assemblage.


la source
1

Toutes les réponses ici semblent exclure un aspect: parfois, nous n'écrivons pas de code pour atteindre un objectif spécifique, mais pour le simple plaisir . Il n'est peut-être pas économique d'investir du temps pour le faire, mais il n'y a sans doute pas de plus grande satisfaction que de battre l'extrait de code optimisé pour le compilateur le plus rapide en vitesse avec une alternative asm roulée manuellement.

madoki
la source
Lorsque vous voulez juste battre le compilateur, il est généralement plus facile de prendre sa sortie asm pour votre fonction et de la transformer en une fonction asm autonome que vous modifiez. Utiliser inline asm est un travail supplémentaire pour obtenir l'interface entre C ++ et asm correcte et vérifier qu'elle est compilée en code optimal. (Mais au moins lorsque vous le faites simplement pour le plaisir, vous n'avez pas à vous soucier de la défaite des optimisations telles que la propagation constante lorsque la fonction se connecte à quelque chose d'autre. Gcc.gnu.org/wiki/DontUseInlineAsm ).
Peter Cordes
Voir aussi les questions / réponses sur le C ++ conjecture de Collatz et asm manuscrites pour en savoir plus sur le fait de battre le compilateur pour le plaisir :) Et aussi des suggestions sur la façon d'utiliser ce que vous avez appris pour modifier le C ++ pour aider le compilateur à faire un meilleur code.
Peter Cordes
@PeterCordes Donc ce que vous dites, c'est que vous êtes d'accord.
madoki
1
Oui, asm est amusant, sauf que asm en ligne est généralement le mauvais choix, même pour jouer. C'est techniquement une question asm en ligne, il serait donc bon d'aborder au moins ce point dans votre réponse. De plus, c'est vraiment plus un commentaire qu'une réponse.
Peter Cordes
OK d'accord. J'étais un gars uniquement asm mais c'était les années 80.
madoki
-2

Un compilateur C ++, après optimisation au niveau organisationnel, produirait du code qui utiliserait les fonctions intégrées du processeur ciblé. HLL ne dépassera ou ne surpassera jamais l'assembleur pour plusieurs raisons; 1.) HLL sera compilé et sorti avec le code Accessor, la vérification des limites et éventuellement intégré dans le ramasse-miettes (anciennement adressant la portée dans le maniérisme POO) nécessitant tous des cycles (flips et flops). HLL fait un excellent travail ces jours-ci (y compris le C ++ plus récent et d'autres comme GO), mais s'ils surpassent l'assembleur (à savoir votre code), vous devez consulter la documentation du processeur - les comparaisons avec du code bâclé sont très certainement peu concluantes et les langs compilés comme l'assembleur résolvent tous jusqu'au code opérationnel HLL fait abstraction des détails et ne les élimine pas, sinon votre application ne fonctionnera pas si elle est même reconnue par le système d'exploitation hôte.

La plupart du code assembleur (principalement des objets) sont générés en tant que «headless» pour être inclus dans d'autres formats exécutables avec beaucoup moins de traitement requis, donc il sera beaucoup plus rapide, mais beaucoup plus non sécurisé; si un exécutable est sorti par l'assembleur (NAsm, YAsm, etc.), il fonctionnera toujours plus rapidement jusqu'à ce qu'il corresponde complètement au code HLL dans la fonctionnalité, les résultats peuvent être pesés avec précision.

L'appel d'un objet de code basé sur l'assembleur à partir de HLL dans n'importe quel format ajoutera intrinsèquement une surcharge de traitement en plus des appels d'espace mémoire en utilisant la mémoire allouée globalement pour les types de données variables / constantes (cela s'applique à la fois à LLL et HLL). Rappelez-vous que la sortie finale utilise le CPU en fin de compte comme api et abi par rapport au matériel (opcode) et que les assembleurs et les "compilateurs HLL" sont essentiellement / fondamentalement identiques, la seule vraie exception étant la lisibilité (grammaticale).

L'application Hello world console dans l'assembleur utilisant FAsm fait 1,5 Ko (et c'est encore plus petit sous Windows sous FreeBSD et Linux) et surpasse tout ce que GCC peut lancer à son meilleur jour; les raisons sont le remplissage implicite avec nops, la validation d'accès et la vérification des limites pour n'en nommer que quelques-uns. Le véritable objectif est de nettoyer les bibliothèques HLL et un compilateur optimisable qui cible un processeur de manière "hardcore" et la plupart le font ces jours-ci (enfin). GCC n'est pas meilleur que YAsm - ce sont les pratiques de codage et la compréhension du développeur qui sont en question et «l'optimisation» vient après l'exploration novice et la formation et l'expérience intérimaires.

Les compilateurs doivent lier et assembler pour la sortie dans le même opcode qu'un assembleur parce que ces codes sont tout ce qu'un processeur fera à l'exception (CISC ou RISC [PIC aussi]). YAsm a beaucoup optimisé et nettoyé une grande partie des premiers NAsm, ce qui a finalement accéléré toutes les sorties de cet assembleur, mais même dans ce cas, YAsm, comme NAsm, produit des exécutables avec des dépendances externes ciblant les bibliothèques de système d'exploitation au nom du développeur, de sorte que le kilométrage peut varier. En conclusion, C ++ est à un point incroyable et beaucoup plus sûr que l'assembleur pour plus de 80%, en particulier dans le secteur commercial ...

Le corbeau
la source
1
C et C ++ n'ont pas de vérification des limites sauf si vous le demandez, et pas de garbage collection sauf si vous l'implémentez vous-même ou utilisez une bibliothèque. La vraie question est de savoir si le compilateur fait de meilleures boucles (et optimisations globales) qu'un humain. Habituellement oui, à moins que l'humain ne sache vraiment ce qu'il fait et y passe beaucoup de temps .
Peter Cordes
1
Vous pouvez créer des exécutables statiques en utilisant NASM ou YASM (pas de code externe). Ils peuvent tous les deux sortir au format binaire plat, vous pouvez donc leur demander d'assembler vous-même les en-têtes ELF si vous voulez vraiment ne pas exécuter ld, mais cela ne fait aucune différence à moins que vous n'essayiez vraiment d'optimiser la taille du fichier (pas seulement la taille du fichier. segment de texte). Voir un didacticiel Whirlwind sur la création d'exécutables ELF Really Teensy pour Linux .
Peter Cordes
1
Vous pensez peut-être à C # ou std::vectorcompilé en mode débogage. Les tableaux C ++ ne sont pas comme ça. Les compilateurs peuvent vérifier des éléments au moment de la compilation, mais à moins que vous n'activiez des options de renforcement supplémentaires, il n'y a pas de vérification à l'exécution. Voir par exemple une fonction qui incrémente les 1024 premiers éléments d'un int array[]argument. La sortie asm n'a pas de contrôles d'exécution: godbolt.org/g/w1HF5t . Tout ce qu'il obtient est un pointeur rdi, aucune information de taille. C'est au programmeur d'éviter un comportement indéfini en ne l'appelant jamais avec un tableau inférieur à 1024.
Peter Cordes
1
Tout ce dont vous parlez n'est pas un simple tableau C ++ (allouer avec new, supprimer manuellement avec delete, pas de vérification des limites). Vous pouvez utiliser C ++ pour produire un asm / code machine de merde (comme la plupart des logiciels), mais c'est la faute du programmeur, pas celle du C ++. Vous pouvez même utiliser allocapour allouer de l'espace de pile en tant que tableau.
Peter Cordes
1
Liez un exemple sur gcc.godbolt.org de g++ -O3génération de code de vérification des limites pour un tableau simple, ou de tout ce dont vous parlez. C ++ facilite beaucoup la génération de fichiers binaires gonflés (et en fait, vous devez faire attention à ne pas le faire si vous visez des performances), mais ce n'est pas littéralement inévitable. Si vous comprenez comment C ++ se compile en asm, vous pouvez obtenir un code qui n'est qu'un peu pire que ce que vous pourriez écrire à la main, mais avec une incrustation et une propagation constante sur une plus grande échelle que vous ne pourriez gérer à la main.
Peter Cordes
-3

L'assembly peut être plus rapide si votre compilateur génère beaucoup de code de support OO .

Éditer:

Aux votants négatifs: l'OP a écrit "dois-je ... me concentrer sur C ++ et oublier le langage assembleur?" et je maintiens ma réponse. Vous devez toujours garder un œil sur le code généré par OO, en particulier lors de l'utilisation de méthodes. Ne pas oublier le langage d'assemblage signifie que vous passerez périodiquement en revue l'assemblage généré par votre code OO, ce qui, à mon avis, est indispensable pour écrire des logiciels performants.

En fait, cela concerne tout le code compilable, pas seulement OO.

Olof Forshell
la source
2
-1: je ne vois aucune fonctionnalité OO utilisée. Votre argument est le même que "l'assembly pourrait également être plus rapide si votre compilateur ajoute un million de NOP".
Sjoerd
Je n'étais pas clair, c'est en fait une question C. Si vous écrivez du code C pour un compilateur C ++, vous n'écrivez pas de code C ++ et vous n'obtiendrez aucun élément OO. Une fois que vous avez commencé à écrire en vrai C ++, en utilisant des éléments OO, vous devez être très compétent pour que le compilateur ne génère pas de code de support OO.
Olof Forshell
donc votre réponse n'est pas sur la question? (De plus, des clarifications vont dans la réponse, pas dans les commentaires. Les commentaires peuvent être supprimés à tout moment sans préavis, notification ou historique.
Mooing Duck
1
Je ne sais pas ce que vous entendez exactement par «code de support» OO. Bien sûr, si vous utilisez beaucoup de RTTI et autres, le compilateur devra créer de nombreuses instructions supplémentaires pour prendre en charge ces fonctionnalités - mais tout problème de niveau suffisamment élevé pour ratifier l'utilisation de RTTI est trop complexe pour être faisable en écriture dans l'assemblage. . Ce que vous pouvez faire, bien sûr, c'est n'écrire que l'interface externe abstraite en tant que OO, en envoyant du code procédural pur optimisé pour les performances là où c'est critique. Mais, selon l'application, C, Fortran, CUDA ou simplement C ++ sans héritage virtuel pourrait être mieux que l'assemblage ici.
gauche vers
2
Non, du moins pas très probable. Il y a une chose en C ++ appelée la règle de zéro surcharge, et cela s'applique la plupart du temps. En savoir plus sur OO - vous découvrirez qu'au final, cela améliore la lisibilité de votre code, améliore la qualité du code, augmente la vitesse de codage, augmente la robustesse. Aussi pour embarqué - mais utilisez C ++ car il vous donne plus de contrôle, embarqué + OO à la manière de Java vous coûtera cher.
Zane