web-dev-qa-db-fra.com

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

J'ai essayé de comparer les performances du langage d'assemblage inline et du code C++. J'ai donc écrit une fonction qui ajoute deux tableaux de taille 2000 à 100 000 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, je lance le programme cinq fois pour obtenir les cycles du processeur, ce qui pourrait être considéré comme du temps. Chaque fois, j'appelle uniquement l'une des fonctions mentionnées ci-dessus.

Et voici le résultat.

Fonction de la version de montage:

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 de publication est presque 3,7 fois plus rapide que le code d'assemblage. 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 dois pas faire confiance aux performances du langage Assembly écrit de mes mains, me concentrer sur le C++ et oublier le langage Assembly?

172
user957121

Oui, la plupart du temps.

Tout d'abord, vous partez de l'hypothèse erronée selon laquelle un langage de bas niveau (Assembly dans ce cas) produira toujours un 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 Java? Non, car il existe une autre variable: programmeur. La manière dont vous écrivez le code et la connaissance des détails de l'architecture influencent considérablement les performances (comme vous l'avez vu dans ce cas).

Vous pouvez toujours produire un exemple où le code assemblé à la main est meilleur que le code compilé mais en général c’est un exemple fictif ou une seule routine qui n’est pas un vrai programme de plus de 500 000 lignes de code C++). Je pense que les compilateurs produiront un code d'assembly meilleur 95% de fois et parfois, seulement quelques rares fois (, vous aurez peut-être besoin d'écrire du code d'assembly pour quelques-uns, bref, - hautement utilisé , performances critiques routines ou lorsque vous devez accéder à des fonctionnalités que votre langage de haut niveau préféré n'expose pas. Voulez-vous une touche de cette complexité? Lire 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 secondes (when on peut avoir besoin de jours ).

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

Pour certains microcontrôleurs complexes, même les systèmes les bibliothèques sont écrites en C au lieu de Assembly, car leurs compilateurs produisent un code final de meilleure qualité (et facile à maintenir).

Les compilateurs peuvent parfois tiliser automatiquement certaines instructions MMX/SIMDx par eux-mêmes, et si vous ne les utilisez pas, vous ne pourrez tout simplement pas comparer (d'autres réponses ont déjà très bien examiné votre code d'assemblage). Juste pour les boucles, il s’agit d’un courte liste d’optimisations de boucles de ce qui est communément vérifié par un compilateur (pensez-vous que Pourriez-vous le faire vous-même lorsque votre emploi du temps est décidé pour un programme C #?) Si vous écrivez quelque chose dans Assembly, je pense que vous devez au moins envisager quelques optimisations simples . L'exemple de livre d'école pour les tableaux est de 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 de devoir utiliser le langage Assembly pour une autre raison: le pléthore de différents processeurs . Voulez-vous les soutenir tous? Chacun a une microarchitecture spécifique et quelques jeux d'instructions spécifiques . Ils ont un nombre différent d'unités fonctionnelles et des instructions d'assemblage doivent être organisées pour les conserver toutes occupé. Si vous écrivez en C, vous pouvez utiliser PGO mais dans Assembly, vous aurez alors besoin d'une grande connaissance de cette architecture spécifique (et repenser et refaire tout pour une autre architecture ). Pour les petites tâches, le compilateur généralement le fait mieux, et pour les tâches complexes généralement le travail n'est pas remboursé (et compilateur peut faire mieux quand même).

Si vous vous asseyez et que vous examinez votre code, vous constaterez probablement que vous gagnerez plus en redesign de votre algorithme qu'en traduisant en Assembly (lisez ceci excellent post ici sur SO ), il sont des optimisations de haut niveau (et des astuces pour le compilateur) que vous pouvez appliquer efficacement avant de devoir recourir au langage Assembly. Il vaut probablement la peine de mentionner que, souvent, en utilisant des composants intrinsèques, vous obtiendrez un gain de performance que vous cherchez et que le compilateur sera toujours en mesure d’effectuer la plupart de ses optimisations.

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

241
Adriano Repetti

Votre code d'assemblage n'est pas optimal et pourrait être amélioré:

  • Vous poussez et ouvrez un registre ( EDX ) dans votre boucle intérieure. Cela devrait être sorti de la boucle.
  • Vous rechargez les pointeurs de tableau à chaque itération de la boucle. Cela devrait sortir de la boucle.
  • Vous utilisez l'instruction loop, qui est connu pour être très lent sur la plupart des processeurs modernes (éventuellement dû à l'utilisation d'un livre d'assemblage ancien *)
  • Vous ne tirez aucun avantage du déroulement manuel de la boucle.
  • Vous n'utilisez pas les instructions SIMD .

Donc, à moins que vous n'amélioriez considérablement vos compétences en matière d'assembleur, il n'est pas logique pour vous d'écrire du code assembleur pour améliorer les performances.

* Bien sûr, je ne sais pas si vous avez vraiment reçu l'instruction loop d'un ancien livre de l'Assemblée. Mais vous ne le verrez 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 les livres IMHO, mauvais et obsolètes.

189
Gunther Piez

Même avant de plonger dans Assembly, certaines transformations de code 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 Rotation de boucle :

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 beaucoup mieux en ce qui concerne la localité de la mémoire.

Cela pourrait être optimisé plus loin, en faisant a += b X fois équivaut à faire a += X * b donc 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.

[edit] J'ai découvert que la transformation était effectuée si nous avions le qualificatif restrict vers x et y. En effet, sans cette restriction, x[j] et y[j] pourrait créer un alias au même endroit, ce qui rendrait cette transformation erronée. [end edit]

Quoi qu'il en soit, this est, je pense, la version C optimisée. Déjà c'est beaucoup plus simple. Sur cette base, voici mon crack chez ASM (je laisse Clang le 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 bien peur de ne pas comprendre d'où viennent toutes ces instructions, mais vous pouvez toujours vous amuser et essayer de voir comment elles se comparent ... mais j'utiliserais quand même la version C optimisée plutôt que celle d'Assembly, en code, beaucoup plus portable.

59
Matthieu M.

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.

41

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 de la version finale:

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

Le code Assembly en mode release est presque 2 fois plus rapide que le C++.

33
sasha

Est-ce que cela signifie que je ne devrais pas faire confiance à la performance du langage d'assemblage écrit de mes mains

Oui, c'est exactement ce que cela signifie et c'est vrai pour chaque langue. Si vous ne savez pas écrire un code efficace en langage X, vous ne devez pas vous fier à votre capacité à écrire du code efficace en X. Si vous voulez du code efficace, vous devez utiliser un autre langage.

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

24
jalf

De nos jours, la seule raison d'utiliser le langage Assembly est d'utiliser certaines fonctionnalités non accessibles par le langage.

Ceci s'applique à:

  • Programmation du noyau devant accéder à certaines fonctionnalités matérielles telles que la MMU
  • Programmation haute performance utilisant 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 déclarations distinctes comme d = a / b; r = a % b; avec une seule instruction qui calcule la division et le reste en une fois si elle est disponible, même si C n’a pas cet opérateur.

21
fortran

Il est vrai qu’un compilateur moderne accomplit un travail remarquable en matière d’optimisation du code, mais je vous encourage néanmoins à continuer à apprendre Assembly.

Tout d’abord, vous êtes clairement pas intimidé par cela, c’est un très bon atout, ensuite, vous êtes sur la bonne voie par profilage afin de valider ou de rejeter vos hypothèses de vitesse, vous demandez contribution de personnes expérimentées, et vous disposez du meilleur outil d'optimisation connu de l'humanité: n cervea.

Au fur et à mesure de votre expérience, vous saurez quand et où l'utiliser (généralement les boucles les plus serrées et les plus profondes de votre code, après l'optimisation poussée à un niveau algorithmique).

Pour vous inspirer, je vous recommanderais de consulter les articles de Michael Abrash (si vous n’avez pas eu de ses nouvelles, il est un gourou de l’optimisation; il a même collaboré avec John Carmack à l’optimisation du moteur de rendu logiciel Quake! )

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

19
user1222021

J'ai changé de 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 de la version finale:

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

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

14
sasha

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'assembly avec SSE est 5 fois plus rapide que le code C++

12
salaoshi

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 de le comparer à votre Assemblée native. Je crois que vous verrez quelques astuces de Nice que votre compilateur utilise.

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

Faire:

mov eax,0

coûtent plus de cycles que

xor eax,eax

qui fait la même chose.

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

12
Nuno_147

Le compilateur vous a battu. Je vais essayer, mais je ne donnerai aucune garantie. Je supposerai que la "multiplication" par TIMES est censée en faire un test de performance plus pertinent, que y et x sont alignés sur 16 caractères et que length est un multiple non nul de 4. C'est probablement tout de même vrai.

  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 fais aucune garantie. Mais je serais surpris que cela puisse être fait beaucoup plus rapidement - le goulot d'étranglement ici est le débit de la mémoire, même si tout est un succès de L1.

10
harold

Implémentant aveuglément le même algorithme, instruction par instruction, dans Assembly est garanti plus lent que ce que le compilateur peut faire.

C'est parce que même la plus petite optimisation réalisée par le compilateur est meilleure que votre code rigide, sans aucune optimisation.

Bien sûr, il est possible de battre le compilateur, en particulier 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 résultat approximatif. 4x accélérez, mais dans ce cas, nous devons nous appuyer fortement sur une bonne connaissance du matériel et de nombreuses astuces apparemment contre-intuitives.

6
vsz

En tant que compilateur, je remplacerais une boucle de taille fixe par 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 va supprimer cette ligne. J'espère que quelque chose dans votre tête est maintenant prêt à attacher quelques options d'optimisation en tant que commentaire. Toutes ces optimisations très efficaces rendront le langage compilé plus rapide.

5
Miah

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

4
Luchian Grigore

J'aime cet exemple car il illustre une leçon importante sur le code de bas niveau. Oui, vous pouvez écrire un assemblage aussi rapide que votre code C. C’est vrai sur le plan tautologique, mais pas nécessairement moyen rien. Clairement quelqu'un peut, sinon l'assembleur ne saurait 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 signifie pas 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 jamais envisagées.

4
tylerl

Dans de nombreux cas, la manière optimale d'effectuer une tâche peut dépendre du contexte dans lequel la tâche est effectuée. Si une routine est écrite en langage Assembly, il ne sera généralement pas possible de modifier la séquence d'instructions en fonction du contexte. Comme 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 32 bits ARM, étant donné ce qui précède, le rendrait probablement comme suit:

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 du code assemblé à la main, soit:

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 à la main nécessiteraient 12 octets d'espace de code au lieu de 16; ce dernier remplacerait une "charge" par un "ajouter", ce 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 langue d’Assembly seraient donc légèrement meilleures que la version compilée. D'autre part, supposons que le compilateur sache que certains registres [p. Ex. r5] devait contenir une valeur située dans les 2047 octets de l'adresse souhaitée 0x40001204 [par ex. 0x40001000], et savait en outre que certains autres registres [p. Ex. r7] devait contenir une valeur dont les bits bas é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 intégré. Si set_port_high est écrit en code assembleur, le compilateur devra déplacer r0 (qui contient la valeur de retour de function1) ailleurs avant d'appeler le code d'assemblage, puis de remettre cette valeur à r0 après (depuis function2 attendra son premier paramètre dans r0), donc le code d'assemblage "optimisé" aurait besoin de cinq instructions. Même si le compilateur ne connaissait aucun registre contenant l'adresse ou la valeur à stocker, sa version en quatre instructions (qu'il pourrait adapter pour utiliser tous les registres disponibles - pas nécessairement r0 et r1) l'emporterait sur l'Assemblée "optimisée". -version linguistique. Si le compilateur avait l'adresse et les données nécessaires dans r5 et r7 comme décrit précédemment, function1 ne modifierait pas ces registres et pourrait donc remplacer set_port_high avec une seule instruction strb --quatre instructions plus petites et plus rapides que le code d'assemblage "optimisé à la main".

Notez que le code d'assemblage optimisé manuellement 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 lorsqu'un morceau de code source peut être invoqué à partir de plusieurs contextes [if set_port_high _ est utilisé à cinquante endroits différents dans le code, le compilateur peut décider indépendamment pour chacun d’entre eux de le développer au mieux].

En général, je pense que le langage d'assemblage est susceptible d'apporter les plus grandes améliorations de performances dans les cas où chaque élément de code peut être abordé à partir d'un nombre très limité de contextes, et est susceptible de nuire aux performances dans des endroits où un élément de code le code peut être abordé à partir de nombreux contextes différents. Fait intéressant (et pratique), les cas où Assembly est le plus bénéfique pour la performance sont souvent ceux où le code est le plus simple et le plus lisible. Les endroits où le code de langue d'Assembly se transformerait en désordre sont souvent ceux où écrire dans Assembly offrirait le plus petit avantage en termes de performances.

[Remarque mineure: il existe certains endroits où le code d'assemblage peut être utilisé pour produire un désordre superficiel optimisé; par exemple, un morceau de code que j'ai créé pour le ARM devait extraire un mot de RAM et exécuter l'une des douze routines environ en fonction des six bits supérieurs) de la valeur (plusieurs valeurs mappées à la même routine). Je pense que j'ai optimisé ce code à 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'utilisait à d'autres fins); les 64 entrées font référence à des adresses dans les 256 octets qui la précèdent. Étant donné que la boucle principale avait dans la plupart des cas une duree d'exécution stricte d'environ 60 cycles, l'extraction et la répartition sur neuf cycles ont été très utiles pour atteindre cet objectif. Utiliser une table de 256 adresses 32 bits aurait été plus rapide d'un cycle, mais aurait englouti 1 Ko de très précieux RAM [flash aurait ajouté plus d'un état d'attente]. Utilisation de 64 32 Les adresses en bits auraient nécessité l'ajout d'une instruction pour masquer certains bits du mot extrait et auraient tout de même englouti 192 octets supplémentaires par rapport au tableau que j'ai utilisé. L'utilisation du tableau des décalages sur 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 dédie un registre "à temps plein" pour contenir l'adresse de la table.

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

3
supercat

C++ est plus rapide, sauf si vous utilisez un langage d'assemblage avec une connaissance plus approfondie et correcte.

Lorsque je code en 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 RAM lorsque je code dans ASM, par exemple: il peut y avoir plus de 20000 lignes de code dans ASM et je n'ai jamais utilisé Push/Pop.

Vous pourriez éventuellement sauter au milieu de l'opcode pour modifier vous-même le code et le comportement sans encourir la peine de modifier vous-même le code. L'accès aux registres prend 1 tick (il faut parfois 0,25 ticks) de la CPU. L'accès à la RAM pourrait prendre des centaines.

Lors de ma dernière aventure avec ASM, je n'ai jamais utilisé la RAM pour stocker une variable (pour des milliers de lignes d'ASM). ASM pourrait être potentiellement incroyablement 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 le C # et le C++ parce que j'ai réalisé que la productivité importait !! Vous pouvez essayer de faire les programmes les plus rapides imaginables en utilisant uniquement de l’ASM pur 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 performances, 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 vitesse d'assembleur seul sur le métal nu est irréfutable. Pourrait-il être encore plus lent en C++? - C'est peut-être parce que vous écrivez du code Assembly avec un compilateur qui n'utilise pas d'assembleur pour commencer.

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

2
user5921954

Ces dernières années, toutes les optimisations de vitesse que j'ai effectuées remplaçaient un code lent endommagé par un cerveau avec un code raisonnable. Mais la rapidité était vraiment critique et je me suis efforcé de faire quelque chose de rapide, le résultat a toujours été un processus itératif, chaque itération donnant une meilleure idée du problème, trouvant des moyens de le résoudre avec moins d'opérations. La vitesse finale dépendait toujours de la perspicacité avec laquelle je me suis mis au problème. Si, à un moment donné, j'utilisais un code d'assemblage ou un code C trop optimisé, le processus de recherche d'une meilleure solution en aurait souffert et le résultat final serait plus lent.

2
gnasher729

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 pur fun. 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 en vitesse l'extrait de code optimisé pour le compilateur le plus rapide avec une alternative à la main.

0
madoki