Partage
  • Partager sur Facebook
  • Partager sur Twitter

[C++] new[]/delete[] vs malloc/free...

    23 janvier 2007 à 21:11:32

    Salut à tous

    Je me suis récemment mis à QT pour tenter de faire un petit prog :)
    Vient le moment de l'allocation dynamique.
    Je veux créer un tableau d'instances de classes (je sais pas si les termes sont exactes, reprennez moi si ce n'est pas correct).
    La class en question se nomme Unit.
    Je suis en C++, je pense naturellement à new[] et delete[].
    Seulement, je constate un ralentissement à la libération avec delete[]...

    Un bout de code sera plus clair :


    Unit* attUnit = NULL;
    attUnit = (Unit*)malloc(sum * sizeof(Unit));
    //tout le bazar de je vous zappe
    free(attUnit);


    Agit tout à fait normalement, tandis que :

    Unit* attUnit = NULL;
    attUnit = new Unit[sum];
    //...
    delete[] attUnit;


    est TRES lent. J'ai "chronométré" l'exécution de ce bout de code pour voir ou ca n'allait pas, et les secondes perdues le sont à l'appel de delete[], alors que free est immédiat!

    D'où ma question simple : pourquoi?
    Merci pour vos réponses ;)
    • Partager sur Facebook
    • Partager sur Twitter
      23 janvier 2007 à 21:26:20

      si je ne raconte pas de bêtise, free se contente de libérer la mémoire, tandis que delete appelle les destructeurs des objets à libérer puis libère la mémoire.
      Si tes destructeurs contiennent beaucoup d'instructions lentes, alors ça peut venir de là.
      • Partager sur Facebook
      • Partager sur Twitter
        23 janvier 2007 à 21:36:28

        Utilise delete[] et puis c'est tout ! Nan mais c'est quoi ces idées de faire un remix entre langages, ce ne sont PAS les mêmes ! Toute tentative de "bidouillage" affreux aura des conséquences !
        • Partager sur Facebook
        • Partager sur Twitter
          23 janvier 2007 à 21:36:36

          Mes constructeur et destructeur:
          Unit::Unit()
          {
          }

          Unit::~Unit()
          {
          }


          Ca me semble pas trop lent ^^
          Il me semblait aussi que delete[] appelait les destructeurs, mais au vu des miens, j'ai de suite rejeté cette hypothèse.

          Merci quand même!
          Si quelqu'un a une autre idée...

          Citation : remram44

          Utilise delete[] et puis c'est tout ! Nan mais c'est quoi ces idées de faire un remix entre langages, ce ne sont PAS les mêmes ! Toute tentative de "bidouillage" affreux aura des conséquences !



          Sans problèmes avec delete[], je l'aurai utilisé. Mais ayant eu mon problème, j'en ai cherché la cause et ai du, c'est vrai, "bidouiller". Si on m'explique pourquoi delete[] me ralentit mon prog, je l'utilise!
          • Partager sur Facebook
          • Partager sur Twitter
            23 janvier 2007 à 21:45:30

            lol ... je te rapelle que quand tu compile tu include plein de classe que tu immagine pas.
            Comme tout celle d'I/O et elles aussi doivent etre detruite si necessair

            De plus comme deja dit melange ce n'est pas bon...
            Et si tu voit telment de difference... et bien ce que tu a couille grave ^^"
            • Partager sur Facebook
            • Partager sur Twitter
              23 janvier 2007 à 21:50:30

              Merci, ca je sais...
              Mais dans ce cas précis, j'ai les mêmes includes, et j'ai "couille grave" lorsque j'utilise delete[]. Je voudrais juste comprendre pourquoi?
              Je veux pas faire de mélanges, je veux utiliser delete[], ici j'ai utilisé free pour comparer, c'est tout!

              Arrêtez de me dire de pas mélanger, j'aurais pu balancer "j'utilise delete[] et c'est lent" -_-
              • Partager sur Facebook
              • Partager sur Twitter
                23 janvier 2007 à 22:04:11

                Ca t'a été expliqué plus haut : delete[] appelle les destructeurs, c'est la seule différence à ma connaissance. Si les destructeurs sont lents (surtout dans un tableau) ça s'explique facilement.

                Après, le destructeur de TA classe est peut-être court, mais si tu utilises d'autres objets dans ta classe (si ça se trouve des tableaux de string ou d'autres objets), il y aura autant d'appels de destructeurs qui EUX font quelque chose !
                • Partager sur Facebook
                • Partager sur Twitter

                If you'd like to join us, read "How do we work at OpenClassrooms"! :)

                  23 janvier 2007 à 22:10:02

                  Ouf, une réponse claire :)
                  Seulement, ma classe est sensée faire des claculs... je n'ai que des double en gros...
                  Bon je vois ca demain matin, j'ai la flemme de m'y replonger là, mais je vais tout commenter dans ma class et voir la différence.

                  Merci à tous!
                  • Partager sur Facebook
                  • Partager sur Twitter
                    23 janvier 2007 à 22:39:49

                    Rassures moi, tu utilises des options d'optimisation du compilateur ? S'il appelle réellement le destructeur à chaque fois c'est sûr que ça va ramer...
                    • Partager sur Facebook
                    • Partager sur Twitter
                      23 janvier 2007 à 22:56:06

                      J'avais oublié de les remettre sur ma nouvelle install, ayant réinstallé Windows récemment, mais -O -O1 -O2 -O3 n'y font rien...
                      • Partager sur Facebook
                      • Partager sur Twitter
                        23 janvier 2007 à 23:13:49

                        mmm tu pourrait poster le code pour voir si y a pas une librairie que tu utilise ou un autre truc qui est effectivement lent, mais ca me semble quand meme etrange que tu puisse apercevoire la lenteure (apart la compilation plus longue)
                        • Partager sur Facebook
                        • Partager sur Twitter
                          23 janvier 2007 à 23:45:27

                          * Comment tu mesures le temps que prend ton delete ?

                          * A tout hasard : ne définis pas de destructeur pour un classe où son destructeur ne fait rien. Celui généré par défaut devrait suffir d'après le bout de code que tu nous as montré.

                          * Pas de fonctions virtuelles ? (j'essais de comprendre pourquoi le destructeur aurait un impact)

                          * GCC sous windows ? Ses ports ne sont pas toujours réputés pour produire des exécutables super rapides.

                          NB: je m'etais amusé à faire des mesures de perf pour des allocations/désallocations sur solaris pour des tableaux d'octets. Je n'avais pas noté de différence flagrante entre les new/delete et les *alloc/free

                          PS: si tes allocations ont lieu dans une boucle, tu as pas mal à gagner en faisant des resize() sur des vecteurs.

                          PPS: les *alloc sont strictement incompatibles avec des objets -- sauf feinte de construction/destruction placées comme les vecteurs les réalisent.
                          • Partager sur Facebook
                          • Partager sur Twitter
                          C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                            23 janvier 2007 à 23:49:23

                            Par l'exemple, j'utilise ici le timer haute résolution de Windows pour obtenir un chonométrage très précis. La résolution de ce timer dépend directement de la vitesse du processeur. Pour l'échantillonner, j'appelle la fonction QueryPerformanceFrequency qui me retourne le nombre de top par seconde. Ensuite j'appelle QueryPerformanceCounter pour obtenir le nombre de top depuis le démarrage de la machine, par différence entre deux appels successif je peux mesurer un temps très précis.


                            #include <Windows.h>
                            #include<vector>
                            #include <iostream>

                            using namespace std;

                            #define  NB_ITER 100
                            #define  NB_OBJECTS_PER_ITER 500

                            class Unit
                            {
                                int toto;
                                char titi[35];
                            public:   
                                // ~Unit(){};
                                // ~Unit(){Sleep(1);} // Un ennorme destructeur 1 ms de temps d'execution
                            };

                            struct Result
                            {
                            LARGE_INTEGER m_lResultFree;
                            LARGE_INTEGER m_lResultDeleteScalar;
                            };

                            int main(void)
                            {
                            LARGE_INTEGER lBase,lFin,lResultFree,lResultDeleteScalar;
                            LARGE_INTEGER lTick;
                            double fTicks,fBaseTemps,fResultFree,fResultDeleteScalar;
                            vector< Result> res(NB_ITER);
                            size_t i;
                               
                                Unit * pUnit = NULL;
                               
                                // Echantillonnage du timer haute résolution
                                QueryPerformanceFrequency(&lTick);
                                fTicks = lTick.QuadPart;
                                fBaseTemps = 1 / fTicks;
                               
                                for ( i = 0 ; i < NB_ITER ; i++)
                                {
                                    pUnit = (Unit *)malloc(NB_OBJECTS_PER_ITER*sizeof(Unit));

                                    QueryPerformanceCounter(&lBase);
                                    free(pUnit);
                                    QueryPerformanceCounter(&lFin);

                                    res[i].m_lResultFree.QuadPart = lFin.QuadPart - lBase.QuadPart;

                                    pUnit = NULL;   
                                    pUnit = new Unit[NB_OBJECTS_PER_ITER];
                                   
                                    QueryPerformanceCounter(&lBase);
                                    delete[] pUnit;
                                    QueryPerformanceCounter(&lFin);
                                    res[i].m_lResultDeleteScalar.QuadPart = lFin.QuadPart - lBase.QuadPart

                                    if (!(i % 10))
                                    {
                                        cout << "Step " << i << " free : " << res[i].m_lResultFree.QuadPart ;
                                        cout << " delete: " << res[i].m_lResultDeleteScalar.QuadPart << endl;
                                    }
                                }
                                lResultFree.QuadPart=0;
                                lResultDeleteScalar.QuadPart=0;

                                /* Calcul des temps moyens en Milliseconde */
                                for (i = 0; i < NB_ITER ; i++)
                                {
                                    lResultFree.QuadPart += res[i].m_lResultFree.QuadPart;
                                    lResultDeleteScalar.QuadPart += res[i].m_lResultDeleteScalar.QuadPart;
                                }
                               
                                lResultFree.QuadPart = lResultFree.QuadPart / NB_ITER;
                                lResultDeleteScalar.QuadPart = lResultDeleteScalar.QuadPart / NB_ITER;

                                fResultFree = lResultFree.QuadPart;
                                fResultFree = (fResultFree * fBaseTemps)* 1000;
                               
                                fResultDeleteScalar = lResultDeleteScalar.QuadPart;
                                fResultDeleteScalar = (fResultDeleteScalar * fBaseTemps)* 1000;

                                cout << endl << "Moyennes: " << endl;
                                cout << "free: " << lResultFree.QuadPart << " soit " << fResultFree << " ms " << endl;
                                cout << "delete[]: " << lResultDeleteScalar.QuadPart << " soit " << fResultDeleteScalar << " ms " <<endl; 
                               
                                return 0;
                            }


                            les deux destructeurs en commentaire


                            Step 0 free : 40 delete: 29
                            Step 10 free : 18 delete: 18
                            Step 20 free : 17 delete: 18
                            Step 30 free : 18 delete: 18
                            Step 40 free : 18 delete: 18
                            Step 50 free : 18 delete: 18
                            Step 60 free : 17 delete: 18
                            Step 70 free : 17 delete: 18
                            Step 80 free : 17 delete: 18
                            Step 90 free : 18 delete: 18

                            Moyennes:
                            free: 18 soit 0.00502857 ms
                            delete[]: 18 soit 0.00502857 ms


                            Avec le destructeur vide


                            Step 0 free : 40 delete: 85
                            Step 10 free : 17 delete: 69
                            Step 20 free : 17 delete: 69
                            Step 30 free : 18 delete: 68
                            Step 40 free : 18 delete: 69
                            Step 50 free : 18 delete: 69
                            Step 60 free : 18 delete: 69
                            Step 70 free : 18 delete: 69
                            Step 80 free : 17 delete: 68
                            Step 90 free : 17 delete: 68

                            Moyennes:
                            free: 18 soit 0.00502857 ms
                            delete[]: 69 soit 0.0192762 ms


                            Avec le destructeur qui contient le Sleep(1) (entre 1 et 2 ms sur ma machine) pour simuler le destructeur d'un objet très complexe.


                            Step 0 free : 40 delete: 3495439
                            Step 10 free : 19 delete: 3495668
                            Step 20 free : 19 delete: 3495662
                            Step 30 free : 18 delete: 3495667
                            Step 40 free : 19 delete: 3495661
                            Step 50 free : 19 delete: 3495677
                            Step 60 free : 18 delete: 3495671
                            Step 70 free : 18 delete: 3495671
                            Step 80 free : 19 delete: 3495667
                            Step 90 free : 19 delete: 3495663

                            Moyennes:
                            free: 20 soit 0.0055873 ms
                            delete[]: 3495612 soit 976.552 ms


                            Maintenant je vire les destructeurs et je remplace le tableau par un vecteur que je mets en public pour la suite ;)


                            vector<char> titi;


                            Sans mettre de constructeur

                            Step 0 free : 36 delete: 344
                            Step 10 free : 14 delete: 326
                            Step 20 free : 15 delete: 328
                            Step 30 free : 14 delete: 327
                            Step 40 free : 14 delete: 327
                            Step 50 free : 14 delete: 327
                            Step 60 free : 14 delete: 327
                            Step 70 free : 14 delete: 449
                            Step 80 free : 14 delete: 326
                            Step 90 free : 14 delete: 326

                            Moyennes:
                            free: 14 soit 0.00391111 ms
                            delete[]: 330 soit 0.0921905 ms


                            puis j'ajoute le constructeur suivant:


                            Unit():titi(35){};



                            Step 0 free : 35 delete: 8750
                            Step 10 free : 24 delete: 10708
                            Step 20 free : 25 delete: 10521
                            Step 30 free : 24 delete: 10562
                            Step 40 free : 25 delete: 10790
                            Step 50 free : 25 delete: 10728
                            Step 60 free : 25 delete: 10606
                            Step 70 free : 25 delete: 11074
                            Step 80 free : 25 delete: 10671
                            Step 90 free : 24 delete: 10618

                            Moyennes:
                            free: 25 soit 0.00698413 ms
                            delete[]: 10746 soit 3.00206 ms


                            L'explication du résultat: Le delete est intelligent, il analyse l'objet, dans le premier cas il n'y a pas de destructeur et pas d'objets utilisant un allocateur, il libère directement la mémoire, il va donc à la même vitesse que le free. Dans le second cas il y a un destructeur qui ne fait rien, mais il est quand même obligé de l'appeler (il n'y a pas d'optimisation, sinon il le supprime probablement), il perd donc du temps sur les appels du destructeur. Dans le troisème cas chaque appel du destructeur lui fait perdre entre 1 et 2 ms. En remplaçant le tableau par un vecteur, j'introduis une obligation pour le destructeur scalaire de détruire tous les vecteurs que j'ai créé donc cela prend plus de temps. Ce que l'on peut retenir c'est que mettre un destructeur qui ne fait rien est inutile, et que c'est même contre-productif.

                            Maintenant dans mon code je vais initialiser mes vecteurs avec la boucle suivante placée entre l'allocation et la libération


                                   
                                    for (size_t j = 0; j < NB_OBJECTS_PER_ITER ; j++)
                                    {
                                        for (size_t k = 0; k < 35 ; k++)
                                        {
                                            pUnit[j].titi[k] = k;
                                        }
                                    }


                            Et là c'est la catastrophe car le malloc a alloué la mémoire sans appeler les constructeurs, mes vecteurs ne sont donc pas initialisés. Et donc:


                            Microsoft Visual Studio C Runtime Library has detected a fatal error in cpp_console.exe (*)


                            Conclusion: Ne jamais utiliser malloc et free en C++, comme le montre la première série, on ne gagne rien sur les types simple et on prend des risques sur les classes.


                            (*) Pour obtenir ce message j'ai du compiler en release, car il y a un assert qui débouche sur un breakpoint en débug.
                            • Partager sur Facebook
                            • Partager sur Twitter
                            Mettre à jour le MinGW Gcc sur Code::Blocks. Du code qui n'existe pas ne contient pas de bug
                              24 janvier 2007 à 0:28:52

                              Citation : int21h

                              Par l'exemple, j'utilise ici le timer haute résolution de Windows pour obtenir un chonométrage très précis. La résolution de ce timer dépend directement de la vitesse du processeur. Pour l'échantillonner, j'appelle la fonction QueryPerformanceFrequency qui me retourne le nombre de top par seconde. Ensuite j'appelle QueryPerformanceCounter pour obtenir le nombre de top depuis le démarrage de la machine, par différence entre deux appels successif je peux mesurer un temps très précis.
                              <...>


                              Magnifique. Ca, c'est une réponse intelligente.

                              J'espère qu'à l'occasion, les forums (et tutoriels) C et C++ seront enfin séparés afin qu'on en finisse avec les mélanges entre ces deux langages qui n'ont rien à voir... C'est comme ça sur tous les forums sérieux Usenet ou web. Pourquoi serait-ce différent ici ? Esprit rebelle ?

                              • Partager sur Facebook
                              • Partager sur Twitter
                              Music only !
                                24 janvier 2007 à 2:26:19

                                Citation : int21h

                                #define NB_ITER 100
                                #define NB_OBJECTS_PER_ITER 500


                                C'est faible comme valeur.


                                Citation : int21h

                                Maintenant je vire les destructeurs et je remplace le tableau par un vecteur que je mets en public pour la suite ;)


                                A partir du moment où le type n'est plus un POD, tu n'as plus le droit d'utiliser les *alloc() -- enfin, tu peux le faire, mais ça plantera.

                                Citation : int21h

                                Ce que l'on peut retenir c'est que mettre un destructeur qui ne fait rien est inutile, et que c'est même contre-productif.


                                Je suppose qu'un compilo intelligent aurait pû l'ignorer s'il avait été inliné comme une fonction vide. Avec définition séparée, il aurait fallu que le linker se rende compte que la fonction était vide pour faire sauter l'appel. Ce qui me parait un peu compliqué comme optimisation.

                                Citation : int21h

                                Conclusion: Ne jamais utiliser malloc et free en C++, comme le montre la première série, on ne gagne rien sur les types simple et on prend des risques sur les classes.

                                Ce n'est pas une histoire de prendre des risques, cela ne marchera juste pas avec les types non-POD vu qu'une étape nécessaire aura été oubliée.

                                Une autre optim?
                                std::vector<Unit> vUnit;
                                for (int i=0 ; i!=BEAUCOUP ; ++i) {
                                    vUnit.resize(0); // équivalent d'une libératon
                                    vUnit.resize(tailleDynamiqueQuiNousVaBien);
                                }

                                Le premier appel à resize(0) n'est nécessaire que s'il y a besoin de réinitialiser par default construction tous les éléments. Quand on a une sémantique devaleur, une simple affectation écrasante suffit.
                                Et suivant les allocateurs, cette feinte n'apporte pas grand chose -- surtout si on réalise les 2 resize.
                                NB: c'est l'équivalent du realloc aux détails que:
                                - cela marche aussi avec les types non-POD
                                - cela lêve une exception au lieu de renvoyer null si le redimensionnement est impossible
                                • Partager sur Facebook
                                • Partager sur Twitter
                                C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                                  24 janvier 2007 à 7:24:01

                                  Merci à tous pour ces réponses, qui ont du en plus vous prendre pas mal de temps.

                                  J'ai trouvé d'où vient mon problème...
                                  Comme je débute en C++, j'ai fait hériter ma class Unit d'une autre class beaucoup plus complexe... en passant cette class en amie, plus de problème.

                                  Merci pour vos réponses encore une fois, je n'aurais pas trouvé sans vos indications!
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    24 janvier 2007 à 14:54:43

                                    Citation : Arkhiall

                                    Merci à tous pour ces réponses, qui ont du en plus vous prendre pas mal de temps.

                                    J'ai trouvé d'où vient mon problème...
                                    Comme je débute en C++, j'ai fait hériter ma class Unit d'une autre class beaucoup plus complexe... en passant cette class en amie, plus de problème.

                                    Merci pour vos réponses encore une fois, je n'aurais pas trouvé sans vos indications!



                                    Petite precision, frien de heritage sont 2 chose completement differentes... ^^"
                                    Donc faut bien l'utiliser, et sourtout utiliser le bon dans le bon cas

                                    Mais ca ce une question de POO et pas specialment de C++
                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      24 janvier 2007 à 19:01:17

                                      Je sais bien!
                                      Et dans mon cas, faire hériter était inutile.
                                      Comme je l'ai précisé, je débute en C++. Je faisais du C avant, et je débute donc aussi en POO!
                                      • Partager sur Facebook
                                      • Partager sur Twitter

                                      [C++] new[]/delete[] vs malloc/free...

                                      × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                      × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                      • Editeur
                                      • Markdown