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

Langage C++ Discussion :

Problème de compilation avec l'option O3


Sujet :

Langage C++

  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 96
    Par défaut Problème de compilation avec l'option O3
    Bonjour

    Suite à des discutions sur ce forum, j'ai voulu tester l'option O3 sur un programme qui fonctionnait bien sans. Comme cela ne fonctionnait plus très bien, j'ai fini par cerner le noeud du problème : il semble y avoir un problème quand on introduit une fonction écrite en assembleur. Voila ce que cela donne
    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
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    #include <iostream>
     
    using namespace std;
    #define uint64_t unsigned long long
    #define uint32_t unsigned long
    //------------------------------------------
     
    void print(uint64_t A[], uint32_t N)
    {
    	cout <<"{";
    	for (int k = 0; k < N; ++k){
    		cout<<A[k] <<", ";
    	}
    	cout <<'\b'<<'\b'<<"}"<<endl ;
    }
    ;
    // new T = T + k*A
    // dim A = 2; dim T = 4;
    // T[j] + k*A[j] = u[j] + b*v[j]
    void mul_add(uint64_t k, uint64_t A[], uint64_t T[])
    {
       uint64_t c ;
       __asm__ __volatile__(
          " mov %[A], %%rsi ;"
          " mov %[T], %%rdi ;"
          " mov $0, %[c] ;"           // carry = 0
          " mov $2, %%rcx ;"          // loop counter (j = 2 - rcx)
       " 0: ;"
          " mov (%%rsi), %%rax ;"     // rax = A[j]
          " mul %[k] ;"               // k*A[j] = rax + rdx*b
          " add (%%rdi), %%rax ;"     // rax = rax + T[j] = u[j]
          " adc $0, %%rdx ;"          // rdx = rdx + CF = v[j]
          " add %[c], %%rax ;"        // rax = u[j] + carry -> new T[j]
          " adc $0, %%rdx ;"          // rdx = v[j] + CF -> new carry
          " mov %%rax, (%%rdi) ;"     // new T[j] = rax
          " mov %%rdx, %[c] ;"        // new carry = rdx
          " add $8, %%rsi ;"
          " add $8, %%rdi ;"
       " loop 0b ;"
          " add %[c], (%%rdi) ;"      // new T[2] = T[2] + carry
          :
          : [A] "r" (A)
          , [T] "r" (T)
          , [k] "r" (k)
          , [c] "r" (c)                // carry
          : "cc", "memory", "%rax", "%rcx", "%rdx"
       );
    }
     
    //-----------------------------------
    int main(void)
    {
      uint64_t x = 2;
      uint64_t y = 3755218545365861964ULL;
      uint64_t n = 10203806521854051703ULL;
      uint64_t q = 17966832521566772153ULL;
      uint64_t X[2] = {x,0}, Y[2] = {y,0}, N[2] = {n,0}, T[4] = {0};
     
     //  cout<<endl<<"X = ";print(X,2);
     //  cout<<endl<<"Y = ";print(Y,2);
     //  cout<<endl<<"N = ";print(N,2);
     //  cout<<endl<<"T = ";print(T,4);
     //  cout<<endl<<"------------"<<endl;
       mul_add(x,Y,T);
       cout<<endl<<"T = ";   print(T,4);
     
       return 0;
    }
    Je travaille sous Windows avec Code::Blocks (dernière version) et MinGW_w64 comme compilateur (mais j'ai aussi testé avec Clang).
    Avec ces données, mul_add calcule 2*3755218545365861964 et place le résultat dans T
    Sans O3 le résultat est correct (aussi bien avec Clang qu'avec MinGW_w64.) On obtient
    T = {7510437090731723928, 0, 0, 0}

    Process returned 0 (0x0) execution time : 0.042 s
    Press any key to continue.
    Les problème surviennent avec O3

    ----------- Avec MinGW_w64 -----------------------
    Avec O3 (et toutes les lignes commentées comme ci-dessus), le résultat est correct. On obtient
    T = {7510437090731723928, 0, 0, 0}

    Process returned 0 (0x0) execution time : 0.013 s
    Press any key to continue.
    Mais si on décommente une des lignes (ou plusieurs), on obtient
    Process returned -1073741819 (0xC0000005) execution time : 0.838 s
    Press any key to continue.
    ---------- Avec Clang -------------

    Avec O3 (et toutes les lignes commentées comme ci-dessus), le résultat est faux. On obtient
    T = {15020874181463447856, 0, 0, 0}

    Process returned 0 (0x0) execution time : 0.042 s
    Press any key to continue.
    De plus, si on décommente une ou plusieurs lignes, on obtient, suivant les cas
    Process returned -1073741819 (0xC0000005) execution time : 0.509 s
    Press any key to continue.
    ou bien
    terminate called after throwing an instance of 'std::bad_cast'
    what(): std::bad_cast

    Process returned -1073740791 (0xC0000409) execution time : 0.489 s
    Press any key to continue.
    Cela est juste un exemple. J'ai obtenu d'autres situations tout aussi bizarres
    • une erreur (0xC0000005) si on place un 'exit()' quelque part mais qui disparaît si on place cet 'exit()' un peu plus loin.

    • une variable de boucle réinitialisée, ce qui donne une boucle infinie

    • la modification de la valeur d'un constante, etc.



    Merci pour vos lumières.

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 167
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 167
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par serge17 Voir le message
    De plus, si on décommente une ou plusieurs lignes, on obtient, suivant les cas
    Process returned -1073741819 (0xC0000005) execution time : 0.509 s
    Press any key to continue.
    ou bien
    terminate called after throwing an instance of 'std::bad_cast'
    what(): std::bad_cast

    Process returned -1073740791 (0xC0000409) execution time : 0.489 s
    Press any key to continue.
    Ça s'appelle un crash, utilise le debugger pour voir où ça crash.
    M'enfin quelle idée de faire de l'asm dans un code C ? Pourquoi ne pas faire juste du C++ ?
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Membre Expert

    Homme Profil pro
    Directeur de projet
    Inscrit en
    Mai 2013
    Messages
    1 729
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Service public

    Informations forums :
    Inscription : Mai 2013
    Messages : 1 729
    Par défaut
    Bonjour,

    Cela ressemble à un débordement mémoire. Si c'est le cas, il provient certainement du code assembleur qui peut faire ce qu'il veut. Si cette hypothèse est correcte un agrandissement de X et Y à 4 pourrait être intéressant.

    Que le débordement apparaisse sous des formes diverses (voire pas du tout) selon les options de compilation n'est pas des plus surprenant.

    Par ailleurs, le code assembleur ne sera jamais optimisé par le compilateur, d'une part parce que __volatile__ lui dit "pas touche !", et d'autre part parce que les registres de travail sont rigidifiés ne laissant pas le compilateur les redistribuer à son gré. On se demande ce qui reste à optimiser : les affichages ?

    Salutations
    Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better. (Samuel Beckett)

  4. #4
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 96
    Par défaut
    Merci à tous pour vos réponses

    #Bousk
    Ça s'appelle un crash, utilise le debugger pour voir où ça crash.
    Je vais essayer de voir. Ma question initiale était << Comment le fait de faire 'cout<<endl;' ou pas peut changer quelque chose ? >>
    M'enfin quelle idée de faire de l'asm dans un code C ?
    Je ne sait pas si cela change quelque chose mais c'est de l'asm dans un code C++.
    Pourquoi ne pas faire juste du C++ ?
    En C++, c'est un calcul en multi-précision ; Boost par exemple peut faire le travail mais avec un temps d'exécution bien trop grand. Evidemment, je ne peut pas tester les temps d'exécutions avec l'option O3 !

    # Guesset
    Par ailleurs, le code assembleur ne sera jamais optimisé par le compilateur, d'une part parce que __volatile__ lui dit "pas touche !", et d'autre part parce que les registres de travail sont rigidifiés ne laissant pas le compilateur les redistribuer à son gré. On se demande ce qui reste à optimiser : les affichages ?
    Mon code asm été testé directement et indirectement ne nombreuses fois : il marche parfaitement. D'autre part il est suffisamment commenté pour se rendre compte qu'il fait exactement ce pour quoi il est fait (mais je ne suis pas un expert -- loin s'en faut ! -- et si on peut l'améliorer en quoi que ce soit, je suis preneur).
    Comme tu le fait remarquer, quelle peut bien être l'optimisation ? Il n'empêche quelle détraque tout.
    Cela ressemble à un débordement mémoire. Si c'est le cas, il provient certainement du code assembleur qui peut faire ce qu'il veut. Si cette hypothèse est correcte un agrandissement de X et Y à 4 pourrait être intéressant.
    Effectivement l'erreur 0xC0000005 correspond à une violation de l'espace mémoire. C'est forcément le code asm qui est en cause parce que c'est le seul à faire quelque chose ! Suivant ton conseil j'ai effectué un agrandissement (j'en ai profité pour simplifier l'exemple)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main(void)
    {
      uint64_t x = 5;
      uint64_t y = 3755218545365861964ULL;
      uint64_t X[10] = {x,0}, Y[10] = {y,0}, T[10] = {0};
     //  cout<<endl;
       mul_add(x,Y,T);
       cout<<endl<<"T = ";   print(T,4);
       return 0;
    Avec le '//' cela donne toujours le bon résultat. Petite curiosité supplémentaire : dans le main ci-dessus, si on remplace T(10] par T(12], on obtient un résultat faux.
    Hélas, si on effectue 'cout<<endl;' l'erreur 0xC0000005 est toujours là ...

    # tous : quelqu'un a-t-il essayé d'exécuter ce nano-programme ? (copier-coller = 1s + exécution = 0.5 s)

  5. #5
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 311
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 311
    Par défaut
    Soyons clair. Ce que tu décris est une corruption mémoire.
    Et il y a tout à parier qu'elle vient de l'asm.

    Et comme dit par mes VDD, mettre de l'O3 par dessus de l'asm ne vas pas l'optimiser. a contrario de le mettre sur du C++.

    > Evidemment, je ne peux pas tester les temps d'exécutions avec l'option O3 !

    Tu veux dire que tu n'as pas testé les temps d'exécution d'un code pur C++ en O3?? Tu peux le comparer directement à ton asm en O0, car il ne seras jamais optimisé en O3 comme on te le disait.

    Donne-nous le code C++ équivalent à ton asm et on verra à l'optimiser.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  6. #6
    Membre Expert

    Homme Profil pro
    Directeur de projet
    Inscrit en
    Mai 2013
    Messages
    1 729
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Service public

    Informations forums :
    Inscription : Mai 2013
    Messages : 1 729
    Par défaut
    Bonjour,

    Il est possible que les optimisations O3 passent les arguments par registre. Sous Windows cela donne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    void mul_add2(uint64_t k, uint64_t A[], uint64_t T[])
    {            //      rcx,          rdx,          r8d
    On voit que k correspond à rcx qui est écrasée à la 4e ligne assembleur par la valeur 2. C'est faux même si ici, comme k (aka x) vaut également 2, cela ne devrait pas se voir.

    De la même manière O3 peut choisir de mettre c en registre pour éviter les coûts de la gestion de pile. Il faudrait demander une sortie de l'assembleur pour vérifier si c'est le cas et, si oui, si le registre choisi n'est pas en conflit avec le code assembleur.

    Les niveaux d'optimisation faibles passent les arguments par la pile. Ils n'ont donc pas ce genre de problème.

    L'usage des instruction PCLMULQDQ (Carry-Less Multiplication Quadword) pourrait peut-être simplifier ce calcul. Quoiqu'il en soit, si on recherche l'efficacité, il faudrait au moins déplier la boucle, coûteuse pour seulement 2 étages. De même intégrer c au sein de l'assembleur éviterait la pile et permettrait de déclarer la fonction fastcall (ce qui limiterait les différences de comportement selon le degré d'optimisation).

    Salutations
    Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better. (Samuel Beckett)

  7. #7
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 96
    Par défaut
    Merci à tous pour vos suggestions ; je vais travailler dans ce sens et revenir vers vous avec des éléments de réponses.

    J'aimerais néanmoins que l'on revienne à l'essentiel
    1) le code assembleur fonctionne correctement sans O3
    2) O3 est sans effet sur du code assembleur
    3) le code assembleur ne fonctionne plus avec O3

    Ces 3 propositions sont contradictoires : comme la 1) et la 3) sont des faits, c'est donc la 2) qui est fausse.
    La question est : comment faire en sorte que O3 soit sans effet sur du code assembleur ? Je pensais avoir fait le nécessaire mais manifestement ce n'est pas la cas.
    Question subsidiaire : en quoi le fait de faire 'cout<<endl' ou pas change quelque chose ?

    PS : Il ne vous a pas échappé que cet exemple est juste là pour monter un dysfonctionnement que je n'arrive pas à cerner ; ce n'est pas mon programme qui lui contient beaucoup de ligne en C++ et qui doit donc être optimisé. Et même si, pour mon programme, je pouvais me passer de code assembleur, il me semble intéressant de savoir le faire proprement. S'il est prévu de pouvoir insérer du code asm dans un programme en C++, cela doit bien servir à quelque chose.

  8. #8
    Membre Expert

    Homme Profil pro
    Directeur de projet
    Inscrit en
    Mai 2013
    Messages
    1 729
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Service public

    Informations forums :
    Inscription : Mai 2013
    Messages : 1 729
    Par défaut
    Bonjour,
    Il y aurait contradiction si la fonction était entièrement en assembleur. Mais ce n'est pas le cas. C'est une fonction classique avec un bloc assembleur. L'optimisation altère l'articulation des échanges entre les variables de la fonction (argument, variable locale) et le bloc assembleur.

    Ce n'est donc pas contradictoire. Aucune des hypothèses que j'ai évoquées n'intervient sur le code assembleur. L'optimisation tend à diminuer les accès mémoires et donc à placer plus d'éléments en registre ce qui peut induire des incohérences avec le code assembleur d'origine.

    On n'accède pas de la même manière aux arguments et la variable locale selon qu'ils sont en registres ou sur la pile. Le code assembleur initial suppose que tout est sur la pile. L'optimisation peut rendre cela caduque.

    Salutations
    Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better. (Samuel Beckett)

  9. #9
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 96
    Par défaut
    # Guesset
    D'accord. Dans la fonction mul_add(), je vais examiner de plus près les liens entre la partie C++ et le boc assembleur. Je pensais avoir fait "tout comme il faut" mais si l'option O3 ne touche pas au bloc, par élimination, il ne reste que cela. Faudra voir ensuite comment y remédier. Merci pour cette orientation.

  10. #10
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 167
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 167
    Billets dans le blog
    4
    Par défaut
    En tous cas quand je demande à ChatGPT quels sont les problème sur ta fonction assembleur, il est pas avare en trouvailles...
    ✔ Summary of problems
    Critical

    ❌ Using an input operand ("r") as output (undefined behavior)

    ❌ Not marking RSI/RDI as clobbered

    ❌ Overwriting c incorrectly

    Moderate

    ❌ Loop counter weirdness (2 hardcoded)

    ❌ Assumes T[2] exists

    ❌ Redundant register moves

    Minor

    Code style issues (missing comments, inconsistent spacing, unnecessary semicolons)
    Citation Envoyé par serge17 Voir le message
    S'il est prévu de pouvoir insérer du code asm dans un programme en C++, cela doit bien servir à quelque chose.
    Bof, c'est juste hisrotique, parce que C++ se veut rétrocompatible.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  11. #11
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 311
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 311
    Par défaut
    Le côté:

    a- "c'est trop lent on a fait en asm"
    b- "on a vu qu'on avait pas activé le -O3" (avant de découvrir le bug de l'asm en O3)

    Me laisse fortement soupçonner que les perf insuffisantes du code C++ avaient été mesurées en O0 -- je n'ai pas vu d'infirmation de cette hypothèse

    Du coup... pourquoi ne pas retester le code C++ en O3?
    Au passage, une expérience rapide m'a montré qu'effectivement en O3, les multiplications sont faites sur du QWORD si ont travaille en __int128_t (type non portable comme le laisse sous-entendre les 2 tirets bas).
    Pour bien faire, il faudrait des micro-benchmarks pour comparer la propagation des retenues à la main VS stocker en 64bits et calculer en 128.

    https://godbolt.org/z/oM1TPaa56 https://godbolt.org/z/aoYq6oq4W -- après je ne suis pas sûr que je fais bien ce qui est attendu si T est non nul. Flemme de rajouter d'autres tests.

    EDIT: J'imagine que pour ce genre de traitement, il va y avoir bien plus que deux digits à traiter. Et du coup, il ne faudra peut-être pas s'arrêter à O3 et regarder ce qui est vectorisable. Mais tout ça, GnuMP, boost.multiprecision, etc devraient déjà le faire, non?
    Sans parler que je soupçonne des optim dédiées pour les multiplications et autres FMA.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  12. #12
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 96
    Par défaut
    # Bousk
    Intéressante l'idée d'utiliser ChatGPT. Même s'il y a des choses contestables (de mon point de vue) il ya deux remarques pertinentes dont une essentielle.
    Critical
    1) curieuse remarque concernant un code asm qui n'a pas de output !
    2) essentielle : rsi et rdi sont des registres non volatiles qui doivent être sauvegardés et restaurés. Les "clobberiser" est une façon commode de le faire faire par le compilateur. Ce n'est rien d'autre que la solution du problème !
    3) la variable 'c' est une variable utilisateur : on peut donc s'en servir à notre guise. Je ne comprend pas le << Overwriting c incorrectly >>

    Moderate
    1) au départ, j'avais prévu de faire un nombre de boucles paramétrable. C'est sûr qu'avec 2 boucles seulement on peut s'en passer. Mais qu'y a-t-il de bizarre à cela ? Je ne comprend pas le <<Loop counter weirdness (2 hardcoded)>>
    2) compréhensible du point de vue de chatGPT : il ne peut pas savoir que 4 emplacements mémoire on été réservés et que non seulement T[2] existe mais aussi T[0], T[1] et T[3] existent.
    3) pertinente : on peut se passer des registres rsi et rdi et éviter des tranferts inutiles.

    Minor
    1) <<missing comments>> : pratiquement chaque ligne est commentée. De quoi s'agit-il ?
    2) <<inconsistent spacing>> : certains espaces sont inutiles et ont pour unique objectif une meilleure lisibilité (mais peut-être la notion humaine de lisibilité est quelque chose qui échappe à chatGPT). Mais ils sont systématiquement répétés ligne après ligne et assurément pas de manière inconsistante, incohérente ou que sais je encore.
    3) <<unnecessary semicolons>> : faux ! Par exemple dans " mov %[A], %%rsi ;" si on retire le ';' le programme ne compile pas. On peut le remplacer par '\n' mais il n'est pas inutile.

    As tu des précisions à apporter aux critiques de chatGPT ?

  13. #13
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 96
    Par défaut
    # Luc Hermitte
    Me laisse fortement soupçonner que les perf insuffisantes du code C++ avaient été mesurées en O0
    Tu soupçonnes bien ! Lors de la phase de mise au point je travaille en O0. Je ne me rendais pas compte à quel point l'optimisation O3 peut accélérer les choses ; c'est pourquoi je me suis tourné vers le code asm (c'était aussi pour moi l'occasion de l'apprendre). Même s'il est probable que je l'abandonne dans mon programme, je ne peut me résoudre à lâcher le morceau : je tiens absolument à comprendre ce qui cloche. Je vais examiner à tête reposé le travail que tu as déposé dans Compiler Explorer.

    FMA : je ne connaissais pas la fonction multiply_add() qui fait, pour les flottants, le même travail que mul_add(). La ressemblance des noms n'est pas intensionnelle mais du coup elle tombe bien.

    PS : mul_add() a vocation à être utilisé en boucle et la valeur recherchée est celle de T à l'arrivée. Ici ça coince dès le départ, même avec T = 0.

  14. #14
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 96
    Par défaut
    # tous
    Ce truc commence à m'énerver sérieusement. Voici une nouvelle présentation du problème
    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
    int main(void)
    {
      uint64_t x = 5;
      uint64_t y = 3755218545365861964ULL;
      uint64_t Y[2] = {y,0}, T[4] = {0};
      cout<<endl;
       mul_add(x,Y,T);
       cout<<endl<<"T = ";
       cout<<T[0]<<"  ";
       cout<<T[1]<<"  ";
       cout<<T[2]<<"  ";
       cout<<T[3];
       cout<<endl;
     //  for(int k = 0; k < 4; ++k){cout<<T[k]<<"   ";}
     
       return 0;
    }
    Cela fonctionne parfaitement. Mais si on décommente la dernière ligne (qui ne fait rien de plus que les 4 lignes précédentes), on a droit à l'erreur 0xC0000005. La violation de l'espace mémoire n'a peut-être pas lieu là où on le croyait (1).
    "Bien sûr", si on commente la ligne 'cout<<endl;' tout rentre dans l'ordre !

    PS : j'ai compilé le programme dans tous les cas de figure ; je n'ai rien trouvé. Un "détail" : comme mul_add() n'est utilisé qu'une seule fois, le compilateur (avec O3) la met in line. Je vais essayer de le forcer à passer par la fonction pour voir si cela change quelque chose.

    update : (1) avec la ligne 14, il n'y a pas d'affichage (même pas "T = ") et le degugging me renvoi à la lib iostream. La violation de l'espace mémoire semble due à la fonction 'cout' qui est complètement détraquée par mul_add. Par exemple, voici ce que qui arrive quand à la ligne 14 on remplace '4' par '1'
    T = 329348653119758204|▀tõ┌¶Æ♦☺1|▀tõ┌¶Æ♦☺0|▀tõ┌¶Æ♦☺0
    329348653119758204
    Process returned 0 (0x0) execution time : 0.084 s
    Press any key to continue
    Ce que je ne comprends toujours pas c'est comment la ligne 14 peut agir sur les lignes précédentes.

  15. #15
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 311
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 311
    Par défaut
    Bien vu.
    Ca me parait bien plus plausible que des changements de convention d'appel entre les modes debug et release -- je sais que ce n'est pas le cas avec l'ABI Itanium, pour Windows, l’hypothèse me paraissait bizarre avec des types primitifs.
    Et donc oui, si il y a inlining, obligatoirement les registres ne seront plus en face. Ca donne quoi si tu désactives l'inlining sur cette fonction.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  16. #16
    Membre Expert

    Homme Profil pro
    Directeur de projet
    Inscrit en
    Mai 2013
    Messages
    1 729
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Service public

    Informations forums :
    Inscription : Mai 2013
    Messages : 1 729
    Par défaut
    Bonjour,

    Quand rien n'est spécifié, il y a un mode par défaut, mais les optimisations peuvent passer outre pour le passage des arguments comme pour la demande d'inline. Je n'avais pas pensé que le compilateur activerait l'inline car le bloc assembleur est conséquent, mais c'est vrai que la fonction n'est pas une fonction intégralement assembleur.

    En termes de communication avec le code assembleur, l'inline est aussi délicat que les optimisations que j'imaginais. En général, j'évite le passage par nom de variable dans un code assembleur. C'est pratique, mais cela cache l'usage de registres et les modes d'accès. Et cet usage peut changer sans que le code assembleur ne change. Par exemple, transformons la viable c en variable globale, le codee asm peut rester le même, mais l'accès à c ne passe plus par la pile : i.e. le code assembleur réel est modifié.

    Il est possible d'avoir le code assembleur généré ? De préférence en syntaxe Intel ? Merci.

    Salutations
    Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better. (Samuel Beckett)

  17. #17
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 96
    Par défaut
    Eureka ! j'ai tout compris. Enfin non, pas tout mais l'essentiel je crois.
    La notion fondamentale est celle de volatilité. Les variables rsi et rdi sont non volatiles et doivent être sauvegardées et restaurées par le code asm.
    https://learn.microsoft.com/fr-fr/cp...aved-registers
    L’ABI x64 considère les registres RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 et XMM6-XMM15 comme non volatils. Ils doivent être enregistrés et restaurés par une fonction qui les utilise.
    Une façon commode de le faire est de les "clobberiser" : le compilateur se charge de tout. Je ne connaissais pas cette utilisation. Pour moi mettre un registre clobber c'était juste éviter que le compilateur s'en serve pour y stocker une donnée, le mettre à l'abri en quelque sorte.
    # Bousk: J'ai modifié ma première réponse qui ne rendait pas justice à chatGPT. Toutes mes excuses que tu transmettras ...

    Avec cela, aussi imparfaite que puisse être ma fonction, elle fonctionne en in line ou non. Je vais la simplifier (en autre : déplier la boucle et supprimer les registres rsi et rdi) en attendant peut-être de la remplacer par un code entièrement en C++
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
      t +=  static_cast<uint256_t>(y)*x;
    Voilà, je ne pense pas aller plus loin dans mes investigations, mais il y a quand même des questions qui restent en suspend et que je laisse à vos bons soins.
    - comment la perversion de rsi et rdi agit sur la lib iostream
    - comment la ligne 14 peut agir sur les lignes précédentes
    - si perversion de rsi et rdi il y a, pourquoi cela n'intervient qu'avec O3 seulement ?

    J'ai beaucoup appris de vos interventions et je vous adresse à tous un grand merci. Je vais attendre un peu avant de passer en résolu.

  18. #18
    Membre Expert

    Homme Profil pro
    Directeur de projet
    Inscrit en
    Mai 2013
    Messages
    1 729
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Service public

    Informations forums :
    Inscription : Mai 2013
    Messages : 1 729
    Par défaut
    Bonjour,

    Félicitations. Honte à moi de ne pas avoir tiqué sur l'usage de rsi et rdi.

    rsi et rdi sont très utilisés pour les chaines de caractères, ceci explique peut-être cela.

    L'appel normal de la fonction sauvegarde puis restaure les registres clé sur la pile donc résout, sans le dire, le problème posé par le code asm. O3 en mettant le code en inline fait disparaître cette sauvegarde/restauration.

    Salutations
    Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better. (Samuel Beckett)

  19. #19
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 554
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 554
    Par défaut
    Citation Envoyé par serge17 Voir le message
    Une façon commode de le faire est de les "clobberiser" : le compilateur se charge de tout. Je ne connaissais pas cette utilisation. Pour moi mettre un registre clobber c'était juste éviter que le compilateur s'en serve pour y stocker une donnée, le mettre à l'abri en quelque sorte.
    En fait, « to clobber », ça veut dire « écraser » au sens large. Quand tu les marques ainsi, tu indiques au compilateur que tu vas écraser leur contenu.

    En général, c'est à toi de les sauvegarder proprement si c'est le cas mais comme c'est coûteux à la longue et que c'est quand même le propre de ces registres de manipuler des données d'abord et pas simplement de servir de variables, ce n'est pas « forcément » un problème. Écraser EAX ou RAX, par exemple, est souvent assez sûr. Écraser EBP ou RBP l'est déjà nettement moins.

    Donc indiquer les registres que l'on compte manipuler permet au compilateur soit de les sauvegarder au préalable, soit de directement en utiliser d'autres dans ses propres routines. Mais il faut tenir compte du fait que cela introduit de fait des contraintes qui vont le pousser à utiliser une solution de repli qui n'est donc pas forcément la plus efficace. C'est ce qui fait qu'il est difficile de battre un compilateur moderne sur de petites routines en assembleur. Même si on arrive à l'optimiser localement (en tenant compte des pièges propres à chaque génération de microprocesseur, comme « LOOP » vs DEC ECX ; JNZ label » sur 486 et suivants (il y a trente ans), on ne peut jamais être sûr que ce gain ne sera pas annulé par sa mise en contexte.

  20. #20
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 96
    Par défaut
    Merci aussi à toi Obsidian.
    Je passe en résolu

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

Discussions similaires

  1. Réponses: 8
    Dernier message: 13/05/2011, 20h29
  2. Réponses: 1
    Dernier message: 16/07/2008, 03h34
  3. Réponses: 3
    Dernier message: 04/11/2007, 20h55
  4. [JB9][EJB]Compiler avec Make ou javac ?
    Par _gtm_ dans le forum JBuilder
    Réponses: 4
    Dernier message: 11/07/2003, 16h59
  5. Compilation avec un Makefile
    Par Mau dans le forum GTK+ avec C & C++
    Réponses: 3
    Dernier message: 28/02/2003, 12h30

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