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 :

vector::reserve(), un hack dangereux ?


Sujet :

Langage C++

  1. #1
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut vector::reserve(), un hack dangereux ?
    Salut,

    il y a un truc qui me turlupine depuis quelques jours.

    Observez donc ce rapport quick bench:
    http://quick-bench.com/IHmmxt68FTep6DqRaD3yxRTU7g0

    Je compare trois méthodes de construction et de remplissage d'un std::vector :
    Première méthode (reserve) : Je construis un vector vide, puis je réserve la taille dont j'ai besoin, puis je le remplis
    Deuxième méthode (construct) : Je construis un vector directement alloué, puis je le remplis
    Troisième méthode (cstyle): J'alloue un tableau "c-style", puis je le remplis

    On voit que le reserve est aussi rapide que le cstyle. Ce qui est normal, puisque le reserve fait un malloc.
    Mais on voit aussi que de construire directement le vector est sensiblement plus lent. Ce qui, il me semble, est dû au fait que le constructeur de vector appelle le constructeur par défaut de chaque objet qu'il contient.

    La question que je me pose, dans cette histoire, c'est si la première méthode (reserve) n'est pas dangereuse ?
    Car finalement, ce qu'on fait dans ce cas, c'est juste allouer de la mémoire, mais on a aucun filet. Quels sont les risques qu'impliquent cette façon de faire ?
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  2. #2
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Si tu utilises correctement l'interface de vector il n'y a aucun risque : la mémoire allouée via reserve n'est pas (facilement) accessible, la taille réelle et le nombre d'éléments présents est gérée à part.
    Oui la construction avec le nombre d'éléments fera appel au constructeur de copie de l'élément que tu passes en paramètres, qui est construit par défaut si tu n'en passes aucun.
    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
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    J'ai trouvé un cas qui peut être gênant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    std::vector<int> v;
    v.reserve(100);
    for(int i = 0; i<100 ; ++i)
    	v[i] = i;
     
    cout << v.at(10) << endl;
    Le v.at(10) lève une exception.
    Ça peut être embêtant je ne me rend pas compte. Je ne me souviens pas n'avoir jamais vu vector::at() utilisé en 15 ans de carrière.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  4. #4
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Sauf que tu présentes une mauvaise utilisation.
    reserve alloue de la mémoire mais pas des objets, tu utilises des int donc ça tombe en marche mais ce n'est pas valide.
    at fait un check de la taille et lève une exception, [] n'en fait pas mais son utilisation ici est incorrecte.
    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.

  5. #5
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Il faut prendre l'habitude de checks les préconditions, sinon on peut faire trop facilement ce genre d'erreur. Avec un assert, tu aurais eu l'info que le code n'est pas valide.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    assert(i < size(v));
    v[i] = Dum(i, i+1);
    Avec un push_back, les perfs sont très mauvaises, mais avec un emplace_back, les perfs sont proches de celle de malloc. Cela permet d'éviter les initialisations par défaut des éléments du vector, ce qui prend pas mal de temps. Mais il reste quand meme la mise a jour de la taille du vector.

    Dans tous les cas, ca ne sera jamais aussi rapide qu'un simple malloc. A commencer par le fait qu'ils ne font pas la meme chose : vector est conçu pour changer de taille plusieurs fois, alors que malloc n'a pas a gerer les resize. L'équivalent de malloc serait les VLA ou un unique_ptr<Dum[]> ou ecrire un fixed_vector ou un uninitialized memory.

    Nom : Screen Shot 2019-09-18 at 23.26.11.png
Affichages : 193
Taille : 67,7 Ko

  6. #6
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    Il faut prendre l'habitude de checks les préconditions, sinon on peut faire trop facilement ce genre d'erreur. Avec un assert, tu aurais eu l'info que le code n'est pas valide.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    assert(i < size(v));
    v[i] = Dum(i, i+1);
    Effectivement, là tu montres bien ce qui ne va pas dans mon code. Quand on reserve(), la size du vector de change pas, uniquement sa capacity.
    C'est donc dangereux de faire juste un reserve() sans construire réellement les éléments du vector.

    Citation Envoyé par mintho carmo Voir le message
    Avec un push_back, les perfs sont très mauvaises, mais avec un emplace_back, les perfs sont proches de celle de malloc. Cela permet d'éviter les initialisations par défaut des éléments du vector, ce qui prend pas mal de temps. Mais il reste quand meme la mise a jour de la taille du vector.
    Je suis curieux de voir le code que tu as utilisé pour ton test. Parce que ces derniers temps, j'ai eu des mauvaises surprises en profilant du code utilisant du emplace_back(), et je me pose actuellement pas mal de questions.

    Citation Envoyé par mintho carmo Voir le message
    Dans tous les cas, ca ne sera jamais aussi rapide qu'un simple malloc.
    Effectivement, et c'est toujours bien de le rappeler.
    Les jeunes arrivent la tête pleine de POO, et refusent de croire des choses aussi évidentes que celles-là. Comme le prix de l'héritage (vtable), ou des architectures trop complexes quand on peut faire simple. Mais je m'égare...
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  7. #7
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 565
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 565
    Points : 7 648
    Points
    7 648
    Par défaut
    Citation Envoyé par r0d Voir le message
    J'ai trouvé un cas qui peut être gênant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    std::vector<int> v;
    v.reserve(100);
    for(int i = 0; i<100 ; ++i)
    	v[i] = i;
     
    cout << v.at(10) << endl;
    Le v.at(10) lève une exception.
    Ça peut être embêtant je ne me rend pas compte. Je ne me souviens pas n'avoir jamais vu vector::at() utilisé en 15 ans de carrière.
    Ecrire dans un vector<> qui est vide est en effet plutôt "osé".
    Peux-être as-tu confondu la ligne 2 avec v.resize(100); qui lui aurait préparé un vector<> de 100 élements. Et alors les v[i] deviennent possibles et évidement le at() aussi.

  8. #8
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Non justement, j'essayais d'évaluer les risques de cette pratique. C'est la raison pour laquelle je parle de "hack" dans le titre.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  9. #9
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Mais c'est pas une pratique, c'est juste du code de quelqu'un qui n'a rien compris à l'interface de vector...
    Tu peux aussi écrire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    std::unique_ptr<int> myint;
    *myint = 5;
    Et tu vas avoir des problèmes, pour autant c'est pas la faute à unique_ptr, c'est le programmeur qui est un abruti.
    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.

  10. #10
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Le cas n'est pas exactement identique à unique_ptr non initialisé, puisque dans le code avec reserve, la mémoire est bien allouée et appartient a std::vector. Le problème est le non respect des contrats, mais je ne serais pas surpris que ce hack tombe en marche.

    Les jeunes arrivent la tête pleine de POO, et refusent de croire des choses aussi évidentes que celles-là. Comme le prix de l'héritage (vtable), ou des architectures trop complexes quand on peut faire simple.
    @rod Le problème n'est pas la POO. C'est d'utiliser un outil pour faire autre chose que ce pour quoi il a été conçu. Ça serait une classe qui fait ce que tu veux, la POO serait une bonne approche et éviterait les problèmes de malloc.

    EDIT

    Idem pour la vtable, l'héritage et les architectures plus complexes. Si on a besoin de polymorphisme, il faudra dans tous les cas assumer le coût d'une vtable ou de l'équivalent que l'on va devoir écrire en C. Se dire qu'on ne va pas utiliser l'héritage par peur des coûts et se retrouver a perdre du temps à implémenter soi meme un équivalent qui ne fera pas forcement mieux, c'est tres discutable. Et on ne pourra pas profiter de la dévirtualisation automatique, il faudra la faire à la main.
    Et "faire simple" n'est pas le seul critère pour le choix d'une architecture. Par exemple, faire une archi qui est plus maintenable pour un cout pas très élevé n'est pas forcément une mauvaise chose.



    Pour le code que j'ai utilisé, c'est simplement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    v.emplace_back(Dim(i, i+1));

  11. #11
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    Pour le code que j'ai utilisé, c'est simplement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    v.emplace_back(Dim(i, i+1));
    Normalement emplace_back est appelé avec les paramètres à utiliser dans le constructeur de l'objet.
    Si v est un vector<Dim> alors un appel plus correct serait v.emplace_back(i, i+1);
    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.

  12. #12
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Merci pour la correction. Effectivement, j'avais fait le test avec push_back et emplace_back (cf le graphique) et j'ai fait la confusion des syntaxes lorsque j'ai écrit mon message précédent. (Pas pratique d'écrire sur un telephone)

    Donc :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    // version avec push_back
    v.push_back(Dim(i, i+1));
     
    // version avec emplace_back
    v.emplace_back(i, i+1);

  13. #13
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    @rod Le problème n'est pas la POO. C'est d'utiliser un outil pour faire autre chose que ce pour quoi il a été conçu.
    Oui oui nous sommes d'accord. La poo permet beaucoup de bonnes choses. Je suis d'ailleurs un de ses fervents défenseurs.
    Je disais juste, en digressant quelque peu, que malgré la puissance de la poo, ce n'est pas toujours non plus la meilleure solution, et qu'on a parfois tendance à oublier cette évidence. Ce n'est pas pour rien si les modèles orientés data ont le vent en poupe ces derniers temps. La nouvelle archi ECS dans Unity en est un parfait exemple.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

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

Discussions similaires

  1. reserve d'un std::vector impossible
    Par Sahengette dans le forum Langage
    Réponses: 15
    Dernier message: 08/03/2012, 20h02
  2. vector reserve & accession
    Par Julgood dans le forum SL & STL
    Réponses: 3
    Dernier message: 19/03/2009, 01h19
  3. equivalent Vector du jsp
    Par Djib dans le forum ASP
    Réponses: 4
    Dernier message: 05/12/2003, 08h07
  4. "vector" provoque "syntax error", malgré
    Par seenkay dans le forum Autres éditeurs
    Réponses: 5
    Dernier message: 24/08/2003, 03h21
  5. Réponses: 2
    Dernier message: 11/07/2003, 18h24

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