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?
la source
Réponses:
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.
la source
Votre code d'assemblage est sous-optimal et peut être amélioré:
loop
instruction, 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 *)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
loop
instructions 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 émettreloop
, vous ne le voyez que dans des livres mauvais et obsolètes à mon humble avis.la source
loop
(et de nombreuses instructions "obsolètes") si vous optimisez pour la tailleMême avant de plonger dans l'assemblage, il existe des transformations de code qui existent à un niveau supérieur.
peut être transformé en via Loop Rotation :
ce qui est bien meilleur en ce qui concerne la localité mémoire.
Cela pourrait être optimisé davantage, faire
a += b
X fois équivaut à le fairea += X * b
, nous obtenons: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
restrict
qualificatif àx
ety
. En effet sans cette restriction,x[j]
ety[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):
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.
la source
x
ety
. Autrement dit, le compilateur ne peut pas être sûr que pour tousi,j
en[0, length)
nous avonsx + i != y + j
. S'il y a chevauchement, l'optimisation est impossible. Le langage C a introduit lerestrict
mot - 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.__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 pourpmulld
: 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.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.
la source
J'ai corrigé mon code asm:
Résultats pour la version Release:
Le code d'assemblage en mode version est presque 2 fois plus rapide que le C ++.
la source
xmm0
place demm0
), vous obtiendrez une autre accélération d'un facteur deux ;-)paddd xmm
(après avoir vérifié le chevauchement entrex
ety
, 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é dansmain
, 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 avecgcc -O3 -march=native
, vous pouvez obtenir 256 bits ou 512 bits vectorisation.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.
la source
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 à:
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.la source
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!)
la source
J'ai changé le code asm:
Résultats pour la version Release:
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
la source
shr ecx,2
est superflu, car la longueur du tableau est déjà donnée enint
et non en octet. Vous atteignez donc fondamentalement la même vitesse. Vous pouvez essayer lapaddd
réponse de harolds, ce sera vraiment plus rapide.c'est un sujet très intéressant!
J'ai changé le MMX par SSE dans le code de Sasha
Voici mes résultats:
Le code d'assemblage avec SSE est 5 fois plus rapide que le C ++
la source
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:
coûte plus de cycles que
qui fait la même chose.
Le compilateur connaît toutes ces astuces et les utilise.
la source
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
y
etx
sont alignés sur 16, et c'estlength
un multiple non nul de 4. C'est probablement tout vrai de toute façon.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.
la source
mov ecx, length, lea ecx,[ecx*4], mov eax,16... add ecx,eax
puis 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.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.
la source
En tant que compilateur, je remplacerais une boucle de taille fixe pour de nombreuses tâches d'exécution.
produira
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.
la source
a
soit volatile, il y a de fortes chances que le compilateur fasse justeint a = 13;
depuis le tout début.C'est exactement ce que cela signifie. Laissez les micro-optimisations au compilateur.
la source
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.
la source
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:
Un compilateur pour le code ARM 32 bits, étant donné ce qui précède, le rendrait probablement comme quelque chose comme:
ou peut-être
Cela pourrait être légèrement optimisé dans un code assemblé à la main, comme:
ou
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:
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:
Pas du tout invraisemblable lors du codage pour un système embarqué. Si
set_port_high
est écrit en code d'assemblage, le compilateur devrait déplacer r0 (qui contient la valeur de retour defunction1
) ailleurs avant d'appeler le code d'assemblage, puis déplacer cette valeur vers r0 par la suite (carfunction2
attendra 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_high
avec 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_high
est 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:
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.
la source
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.
la source
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:
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
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.
la source
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 ...
la source
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 .std::vector
compilé 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'unint 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 pointeurrdi
, 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.new
, supprimer manuellement avecdelete
, 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 utiliseralloca
pour allouer de l'espace de pile en tant que tableau.g++ -O3
gé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.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.
la source