IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

x86 32-bits / 64-bits Assembleur Discussion :

[MMX] Optimisation d'un code C++ -> plus lent


Sujet :

x86 32-bits / 64-bits Assembleur

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Rédacteur
    Avatar de Laurent Gomila
    Profil pro
    Développeur informatique
    Inscrit en
    Avril 2003
    Messages
    10 651
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Avril 2003
    Messages : 10 651
    Par défaut [MMX] Optimisation d'un code C++ -> plus lent
    Bonjour

    Je ne suis pas vraiment un pro des langages d'assemblage, mais récemment j'ai tenté d'optimiser un bout de code C++ critique se prétant visiblement bien à une certaine parallelisation.

    Il s'agit de traiter des entiers dans une boucle 3 par 3, j'ai donc pensé que MMX me permettrait d'avoir une instruction pour 3 opérations.

    Le code fonctionne très bien, cependant il est plus lent que l'original. Avez-vous une idée de ce qui peut clocher ?

    Voici le code (j'utilise des fonctions intrinsèques pour le MMX, si vous voulez les instructions ASM équivalentes je peux les donner), le code original associé à chaque instruction MMX est donné en commentaire à côté. Etant donné que je ne traite que 3 entiers à la fois, le 4ème élément est simplement une copie du troisième pour garder des calculs cohérents.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    _mm_empty();
     
    __m64 CYmm   = _mm_set_pi16(CY[0], CY[1], CY[2], CY[3]);
    __m64 DXmm   = _mm_set_pi16(DX[0], DX[1], DX[2], DX[3]);
    __m64 DYmm   = _mm_set_pi16(DY[0], DY[1], DY[2], DY[3]);
    __m64 Zeromm = _mm_setzero_si64();
     
    for (int y = MinY; y <= MaxY; ++y)
    {
    	__m64 CXmm = CYmm;
    	//int CX1 = CY1;
    	//int CX2 = CY2;
    	//int CX3 = CY3;
     
    	for (int x = MinX; x <= MaxX; ++x)
    	{
    		//if ((CX1 >= 0) && (CX2 >= 0) && (CX3 >= 0))
    		if (_mm_movemask_pi8(_mm_cmpgt_pi16(Zeromm, CXmm)) == 0)
    		{
    			if (ZValue < ColorBuffer[x])
    				ColorBuffer[x] = ZValue;
    		}
     
    		CXmm = _mm_subs_pi16(CXmm, DYmm);
    		//CX1 -= DY12;
    		//CX2 -= DY23;
    		//CX3 -= DY31;
    	}
     
    	CYmm = _mm_adds_pi16(CYmm, DXmm);
    	//CY1 += DX12;
    	//CY2 += DX23;
    	//CY3 += DX31;
     
    	ColorBuffer += m_Size;
    }
     
    _mm_empty();
    Aussi, si vous voyez des erreurs ou des choses à optimiser, n'hésitez pas, je ne connais le MMX que depuis 2 jours

    Pour info, le code original en C++ est mesuré à 60 ms, le code MMX à 100 ms. La procédure de test n'est je pense pas en cause : le temps d'exécution est renvoyé par un profiler, la fonction tournant en temps réel dans un moteur 3D.

    Si cela peut vous aider, je peux aussi donner le code assembleur généré par le compilo.

    Merci d'avance à tous.

  2. #2
    Inactif
    Profil pro
    Inscrit en
    Avril 2006
    Messages
    130
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2006
    Messages : 130
    Par défaut
    Ce n'est pas une réflexion qui t'est destinée, elle est générale :

    Oui, juste pour rigoler... envoies la purée du compilo... si si je suis sérieux !

    Le plus "amusant" c'est que le compilateur joue à cache-cache avec toi.

    Le moindre mal (mais alors vraiment, le moindre mal) serait de faire ça en INLINE pour augmenter ta surface de maîtrise.

    Le fait d'utiliser MMX n'est pas magique (je vais finir par croire que c'est une pensée commune, voir un lieu commun...) et la // pose le problème de l'entrelacement efficace : Travailler en // ne sert de rien si tu n'utilises pas le temps libre... et ça aucun VTUNE ou autre "justificateur" ne te le montrera vraiment, c'est surtout pas leur but mais de justifier l'emploi des librairires et autres boites noires payantes qui rendent de plus en plus captifs ceux qui s'y perdent... ...

    Il semblerait que tu connaisses OpenGL ou/et ses équivalents maintenant intégrés dans les cartes graphiques... Pourquoi, alors, ne pas // réèllement et utiliser le matériel pour et pourquoi il est conçu ?

    A croire que l'informatique c'est juste écrire du code (pour pas dire de l'ASCII confié au dieu (plus l'esclave, puisque personne ne regarde ce qu'il fait rééllement !) compilator :

    Pas de machine, pas de code... C'est la machine qui conditionne le code, pas le contraire et la portabilité est une paresse couteuse dans ces temps de Intel Only et de WDM...

    En un mot : La stratégie n'est pas bonne (Je persiste et signe : Elle est un égarement, signe de l'incompréhension actuel des codeurs en général). Pourtant, il sera difficile d'en faire le reproche à M$ qui met (certes discrètement) tout sous les yeux du lecteur de doc attentif.

  3. #3
    Rédacteur
    Avatar de Laurent Gomila
    Profil pro
    Développeur informatique
    Inscrit en
    Avril 2003
    Messages
    10 651
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Avril 2003
    Messages : 10 651
    Par défaut
    Oui, juste pour rigoler... envoies la purée du compilo... si si je suis sérieux !
    Ok, je posterai le code généré demain matin (il est au boulot).

    Le plus "amusant" c'est que le compilateur joue à cache-cache avec toi.
    C'est-à-dire ?

    Le moindre mal (mais alors vraiment, le moindre mal) serait de faire ça en INLINE pour augmenter ta surface de maîtrise.
    C'est possible effectivement, mais ce sera un peu plus laborieux pour moi qui ne maîtrise pas l'ASM. Tu peux me dire en quoi cela améliorerait le schmilblick ?

    Le fait d'utiliser MMX n'est pas magique (je vais finir par croire que c'est une pensée commune, voir un lieu commun...) et la // pose le problème de l'entrelacement efficace : Travailler en // ne sert de rien si tu n'utilises pas le temps libre... et ça aucun VTUNE ou autre "justificateur" ne te le montrera vraiment, c'est surtout pas leur but mais de justifier l'emploi des librairires et autres boites noires payantes qui rendent de plus en plus captifs ceux qui s'y perdent...
    Certes, mais je suis juste parti du constat, a priori simple, que si je remplace mes triplets d'instructions par une seule MMX, il y aura forcément un gain quelque part. Je ne cherche pas forcément à mettre en place une architecture pour paralléliser au mieux mon code, seulement à réduire le nombre d'instructions de ce petit morceau.
    Si ce n'est pas magique, j'aimerais un peu plus d'éclaircissements. C'était le but de mon post : savoir pourquoi c'était plus lent, alors que je m'attends au contraire.

    Il semblerait que tu connaisses OpenGL ou/et ses équivalents maintenant intégrés dans les cartes graphiques... Pourquoi, alors, ne pas // réèllement et utiliser le matériel pour et pourquoi il est conçu ?

    [...]
    Il me semble que tu pars quelque peu en sucette là... Quoiqu'il en soit je ne suis pas sûr d'avoir compris la subtilité du message, cependant si tu veux parler parrallélisme on peut en parler, c'est justement la clé de l'algorithme dont est extrait ce bout de code.

    En gros, il s'agit du coeur d'un rasterizer de triangles simplifié, qui tourne sur le CPU pendant que la carte graphique est occupée à rendre la scène à l'écran. Ainsi si j'arrive à optimiser (entre autre) ce bout de code, via MMX ou autre, j'aurais un algorithme qui s'exécute gratuitement ; pour le moment ce n'est pas tout à fait le cas, le GPU se tourne encore un peu les pouces en attendant la fin de ma rasterization.

    Si cela te paraît toujours une mauvaise stratégie je suis prêt à écouter tes lumières.

  4. #4
    Inactif
    Profil pro
    Inscrit en
    Avril 2006
    Messages
    130
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2006
    Messages : 130
    Par défaut
    - Je pense que cela sera instructif pour plusieurs de jeter un coup d'oeil au code réel. De faire la différence entre intention ASCII et réalité code.

    C'est dans cet esprit que je parle de cache cache...

    Le INLINE (comme vraiment moindre mal minimum) t'éviterais de laisser la main au compilo qui gère très mal les registres la pile etc. car il ne comprends pas la stratégie globale que tu souhaite mettre en oeuvre et utilise des principes beaucoup trop "raides" d'optimisation qui deviennent absurdes par leur utilisation hors contexte... (je constate ça toutes les heures).

    Mais je maintiens (si le post est bien sur un forum ASM) que l'INLINE est tributaire du compilo et du conxtexte stratégique donc : Moindre, moindre mal...

    Pour MMX, il manque une gigogne de //. Pendant que MMX travaille et que les GPU/DVP travaillent que fait le CPU... Un petit problème de synchro et l'avantage visé devient un defaut, ce le cas du code présenté. FPU pose le même problème... wait... wait... wait... (si tu veux on monte un groupe ). La gratuité envisagée à un coût sur le coup qu'il va être difficile de régler efficacement avec un HLL (j'y reviendrais plus loin).

    ...le GPU se tourne les pouces en attendant... Plains-toi ça pourrait-être le contraire

    Donc, le baton est toujours à l'intérieur de la sucette : OpenGniaGnia ou DirectGnionGnion sont maintenant, (via WDM etc. désolé d'enfoncer des portes ouvertes avec des lieux-communs que tu connais par coeur, mais certains lecteurs pourront recadrer les enjeux stratégiques) "intégrés" dans nos carte graphiques. Pour un HLL cela veut dire transcodage opaque de ton code ASCII initial : Il est largement reformulé pour obtenir un code compatible avec des technologies natives (cela est transparent pour des questions de gros sous et de paresse fond de commerce mais la triste réalité est là...). Même le noyau c'est vu enrichi (il a doublé de taille sous XP64) de partie descendues en ASM pour récupérer les perf manquantes et supprimer toute une série de mécanismes lourds, obsolètes, situés au mauvais endroit et ne disposant pas des priorités necessaires... (ça permet aussi de protéger le commerce des outils sans fins...)

    La // des cartes via PCI express (deux cartes graphique qui partagent dynamiquement les ressources etc.) l'accès direct au flux sans utilisation DMA (KS)... Le but de tout ça c'est de // effectivement, mais pas de synchroniser ! (heureusement)
    Il n'est pas possible de // deux traitements interdépendants et partageant un contexte, sans pertes en vitesse (l'echec basique du multitâches)...

    Donc, vouloir "gruger" le compilo en lui machant le boulot, c'est tenter d'essayer de le faire sortir de son contexte et de sa logique de travail (Si tu n'utilises aucune librairie Direct Blabla peut-être (et encore, je te défie de savoir ce qui est réèllement dans ton code )).

    Il me semble qu'une partie basse est dédiée à ce genre de boulot (sinon pourquoi s'ennuyer à court-cicuiter DMA, déplacer le code natif du coté des DSP/DVP...) comme ça tu peu utiliser le CPU pour garder la bière au frais... Mais bon, tu es très au fait de tout ça, je veux pas te prendre la tête...

    Pour faire simple : En C/C++ tu ne pourras jamais (mais alors,vraiment jamais) optimiser ton truc proprement et les bidouilles "infâmes" que tu devras utiliser seront renversées par la prochaîne librairie / dll qui tombera de l'arbre... Sacrée portabilité .

    Ceci dit un petit tour du coté de chez DDK ou, par exemple, comme je le proposait à quelqu'un peiné de n'y trouver que du C et pas de l'ASM GDC2003_VideoShader.pdf (en fait, lire entre les lignes )
    Désolé donc, pour la subtilité du message, c'est tout moi ça... sub-tilt

    Bon code et bravo pour tout le boulot déjà accompli et proposé sur ton site.

  5. #5
    Rédacteur
    Avatar de Laurent Gomila
    Profil pro
    Développeur informatique
    Inscrit en
    Avril 2003
    Messages
    10 651
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Avril 2003
    Messages : 10 651
    Par défaut
    Le INLINE (comme vraiment moindre mal minimum) t'éviterais de laisser la main au compilo qui gère très mal les registres la pile etc. car il ne comprends pas la stratégie globale que tu souhaite mettre en oeuvre et utilise des principes beaucoup trop "raides" d'optimisation qui deviennent absurdes par leur utilisation hors contexte... (je constate ça toutes les heures).
    Ok, je crois comprendre ce que tu veux dire. Par exemple, j'ai regroupé certaines variables dans un tableau afin qu'elles soient contigües en mémoire, en pensant que ça allait simplifier leur placement dans un registre MMX. En observant le code ASM généré, j'ai l'impression que le compilo me les recopie tout de même ailleurs avant d'appeler le MOVQ correspondant.

    Pour MMX, il manque une gigogne de //. Pendant que MMX travaille et que les GPU/VSP travaillent que fait le CPU... Un petit problème de synchro et l'avantage visé devient un defaut, ce le cas du code présenté. FPU pose le même problème... wait... wait... wait... (si tu veux on monte un groupe ). La gratuité envisagée à un coût sur le coup qu'il va être difficile de régler efficacement avec un HLL (j'y reviendrais plus loin).
    Mais MMX, c'est justement le CPU non ? Que pourrais-je lui faire faire en même temps que mes instructions MMX ? Pas des calculs sur flottants visiblement, puisque MMX utilise les registres des flottants. Et cela voudrait donc dire que l'utilisation du MMX est intrinsèquement plus "lente" que le code normal (si mal parallèlisé), même si ça génère moins d'instructions ?

    Ensuite j'ai un peu perdu le fil de ton explication, mais j'imagine que c'est en partie dû au fait que j'ignore ce qu'est un HLL

    Sinon tu parles toujours de GPU et de VSP, mais j'ai du mal à voir le rapport avec mon problème.

    Pour faire simple : En C/C++ tu ne pourras jamais (mais alors,vraiment jamais) optimiser ton truc proprement et les bidouilles "infâmes" que tu devras utiliser seront renversées par la prochaîne librairie / dll qui tombera de l'arbre... Sacrée portabilité .
    Pourtant, toutes les personnes qui ont dû écrire un rasterizer software en C++, l'ont bien optimisé avec du MMX et du SSE. Et le gain constaté est conforme aux attentes naïves que l'on peut avoir : x2 pour les parties MMX, et x4 pour les parties SSE (grosso modo). Et le principe n'était pas compliqué : il s'agissait de regrouper des traitements identiques par 2 ou 4 dans des registres MMX / SSE, et de les balancer en parallèle sur le GPU.
    Donc, pourquoi pas moi ? Comment faire ?

  6. #6
    Inactif
    Profil pro
    Inscrit en
    Avril 2006
    Messages
    130
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2006
    Messages : 130
    Par défaut Cali, la mer, hé ho... the return !
    Mais MMX, c'est justement le CPU non ? Que pourrais-je lui faire faire en même temps que mes instructions MMX ? Pas des calculs sur flottants visiblement, puisque MMX utilise les registres des flottants. Et cela voudrait donc dire que l'utilisation du MMX est intrinsèquement plus "lente" que le code normal (si mal parallèlisé), même si ça génère moins d'instructions ?

    Si MMX et son pote verlent utilise F(PU) cela veut dire que tout ça c'est du co-process, donc... wait... (tu sais l'instruction FPU qui fait mal là où ça fait du bien, enfin un truc différent du Synthol )

    De plus, le code le plus court n'est pas toujours le plus rapide loin s'en faut ! Par contre, ne pas le faire va plus vite : Oui.

    Ensuite j'ai un peu perdu le fil de ton explication, mais j'imagine que c'est en partie dû au fait que j'ignore ce qu'est un HLL

    Humour suite : Pour générer du code machine tu peux utiliser soit un compilateur (Hight Level Language) soit un assembleur...
    Dans l'un tu fais de l'ASCII et tu espères très fort que le compilo (et tous ses occultes copînes) va être sympa et faire pleins de choses que tu veux ignorer, pour que ça tourne...

    Dans l'autre cas, avec un bon outil, ce que tu écrits c'est ce qui sera (tu sais à qui la faute ).

    Sinon tu parles toujours de GPU et de DVP, mais j'ai du mal à voir le rapport avec mon problème.

    Ben c'est le DVP qui fait le boulo, normalement, cad : Pas seulement afficher un écran calculé par le CPU (ce qui serait désastreusement lent... mais y encore des fondus de l'ASM 16 bits pour travailler comme ça, je te rassure... enfin, si ça rassure quelqu'un )
    Le CPU ne fait qu'initialiser le code de celui-ci (l'automate) par moteurs interposés (m'enfin je t'apprends rien). La carte stocke tes données mais aussi tes "traitements". C'est le pôvre CPU qui va s'ennuyer (remarques, si tu veux surveiller le pointeur...).

    COM devrait aussi pas mal t'aider... mais chut...on nous écoute... et j'aimerais pas donner des idées

    Pourtant, toutes les personnes qui ont dû écrire un rasterizer software en C++, l'ont bien optimisé avec du MMX et du SSE. Et le gain constaté est conforme aux attentes naïves que l'on peut avoir : x2 pour les parties MMX, et x4 pour les parties SSE (grosso modo). Et le principe n'était pas compliqué : il s'agissait de regrouper des traitements identiques par 2 ou 4 dans des registres MMX / SSE, et de les balancer en parallèle sur le GPU.
    Donc, pourquoi pas moi ? Comment faire ?

    Je pense que le "pourquoi pas moi" n'est pas seulement une réplique piquée qu'au qu'Ali amère héros... Si tu n'obtiens pas le résultats c'est que :

    - Tu ne compares peut-être pas la même chose opu de la même manière.

    - Les dites comparaisons sont visibles où, histoire de vérifier ? (je ne nommerai pas certains jeux bien connus, écrits entièrement en ASM, qui ne tirent pas leurs performances d'un compilo ou d'une mise en commun de travaille // ...

    - Pour balancer en // au GPU... Je serais curieux de savoir par quel buss PCI express tu passes ... Surtout qu'il faut "sortir" du coprocess et ensuite demander au CPU de causer à DMA qui voudront bien bouger leurs fesses pour ton code qui va se retrouver à la bourre là où il est attendu par sa maman si ça continue à ce faire des coucous, c'est moi, t'es libre, t'es où, tu fais quoi (heureusement les portables sont éteinds dans les buss)...

    - D'où tiens-tu tes chiffres ? As-tu comparé le code ? Ou est-ce encore un délire d'illustre inconnu sans code comparé ? (ça devient la plus sale habitude que je connaise en ces temps d'intox rempantes).

    - Si tu veux obtenir le maximum de résultats en optimisant sérieusement ta stratégie (tu as compris que tu ne peux pas macher le boulot au compilo qui fait qu'askyveu et qui comprend rien à rien si ce n'est que " n'importe comment les perfs c'est plus le code, c'est la machine, elles sont tellement puissantes qu'on a plus besoin de l'ASM ben voyons... elle est rigolote celle-là en plus d'être complètement absurde dans le principe même !!!)
    Le seul et unique moyen sérieux c'est de tout coder en ASM (ou pourquoi pas une dll si vraiment tu as encore des doutes ou simplement un peu la trouille...). De tout loger dans les différentes cartes de traitement et de // des process indépendants le tout en mode kernel.
    Je pense qu'après ça tes copains, et toi aussi j'espère, pleureront devant les perfs et que la taille de ton code devrait-être divisé par 1/10000 (l'exemple de lecteur multidémia proposé dans un autre poste (8Ko) en est une bien pâle illustration. http://www.developpez.net/forums/sho...d.php?t=145351 )

    bon code

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Optimisation d'un code !
    Par leserapheen dans le forum Pascal
    Réponses: 20
    Dernier message: 09/03/2007, 14h00
  2. Assistance au code qui marche plus?
    Par bslota dans le forum Eclipse Java
    Réponses: 6
    Dernier message: 07/03/2007, 14h40
  3. Pourquoi mon code est plus lent que Arrays.sort
    Par alexis779 dans le forum Collection et Stream
    Réponses: 3
    Dernier message: 12/12/2006, 12h44
  4. Code Asm plus lent que le C !!!
    Par themadmax dans le forum x86 32-bits / 64-bits
    Réponses: 7
    Dernier message: 23/01/2006, 18h21
  5. [Firebird][Optimisation]Plus lent que le BDE!
    Par vincentj dans le forum Débuter
    Réponses: 3
    Dernier message: 07/02/2005, 15h48

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo