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

C++ Discussion :

optimiser les prédicats


Sujet :

C++

  1. #1
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut optimiser les prédicats
    Supposons un prédicat (ou foncteur) comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct pred
    {
     bool operator(Ob const & rl,Ob const & rr) const
     {
       resource tmp_r;
       int il=rl.foo(tmp_r);
       int ir=rr.foo(tmp_r);
       return il < ir;
     }
    };
    En gros cela signifie que j'ai besoin d'une resource assez "lourde" mais qui reste temporaire et à usage local uniquement.

    Afin d'éviter sa construction et destruction à chaque passage dans le foncteur, je pense qu'il est préférable de faire comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct pred
    {
     resource tmp_r;
     bool operator(Ob const & rl,Ob const & rr) const
     {
       tmp_r.clear();
       int il=rl.foo(tmp_r);
       int ir=rr.foo(tmp_r);
       return il < ir;
     }
    };
    Ceci étant, ce n'est peut-être pas encore assez satisfaisant car les foncteurs sont toujours passés par valeur à l'intérieur des méandres de la SL, avec recopie probable de la resource tmp_r à chaque fois.

    Il y a une troisième solution:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct pred
    {
     resource & tmp_r;
     pred(resource & r=resource()):tmp_r(r) {}
     bool operator(Ob const & rl,Ob const & rr) const
     {
       tmp_r.clear();
       int il=rl.foo(tmp_r);
       int ir=rr.foo(tmp_r);
       return il < ir;
     }
    };
    Cette troisième écriture semble bonne car la resource n'est créée qu'une fois puis "déplacée" à chaque copie du prédicat, mais elle ne me satisfait pourtant pas pleinement (sans que je puisse dire pourquoi).
    A priori je préfère la 2ème écriture, mais les compilateurs sont-ils assez intelligents dans l'optimisation en mode release pour comprendre que les fonctions de la SL, fonctions inline pour la plupart, une fois mises à "plat" (ou défactorisées, je ne sais pas comment dire), le prédicat passé par valeur est en fait toujours le même ? Sont-ils capables de comprendre qu'il suffit de "déplacer" le predicat plutôt que d'en faire une copie à chaque appel de fonction ?

  2. #2
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Ta troisième solution rend le prédicat non copiable... Elle ne marchera pas. Et si on la faisait marcher (par exemple, en utilisant un shared_ptr au lieu d'une référence pour partager la ressource entre différentes copies du prédicat), elle marcherait, il faudrait juste faire attention à ce que deux threads n'exécutent pas un prédicat avec la même ressource en même temps.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  3. #3
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Il faut qu'il soit copiable, bien sur...
    Voilà, j'ajoute le copy constructor
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    pred(pred const & r):tmp_r(r.tmp_r) {}
    Et on désactivera aussi explicitement l'operator assign.

    Je vais essayer d'exprimer ma question d'un point de vue plus général.
    Supposons un objet Ob. Lorsque cet objet Ob est instantié il crée et utilise une resource locale (que je souhaite conceptuellement comme non partagée ni copiable même si techniquement elle pourrait l'être). Je voudrais que cet resource locale ne soit pas réinstantiée à chaque copie de l'instance de l'objet Ob (la resource est unique pour une instance de Ob et de ses copies éventuelles).
    Voilà pour le cas général.

    Pour en revenir à mon exemple, j'essaie d'imaginer un moyen d'éviter la contrainte de copie imposée lors d'un passage de paramètre par valeur (comme c'est le cas pour les fonctions de la SL).
    Dans bien des cas c'est un "déplacement" qu'il faudrait faire plutôt qu'une copie. J'ai cru comprendre qu'il aura dans une future version de C++ ce genre de possibilité ("move" avec l'operator &&) mais ce n'est pas encore d'actualité. Et puis je suppose que le code de la SL ne sera pas réécrit en remplaçant les copies des prédicats par des déplacements, le problème restera.

    Cependant...

    Et j'en parlais au début, ne suffit-il pas de faire confiance au compilateur et à ses optimisations ?
    Exemple:
    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
    inline void foo3(resource r)
    {
     cout << r << endl;//assume an existing operator<<  for resource
    }
     
    inline void foo2(resource r)
    {
     cout << "foo2 enter" << endl;
     foo3(r);
     cout << "foo2 exit" << endl;
    }
     
    inline void foo1(resource r)
    {
     cout << "foo1 enter" << endl;
     foo2(r);
     cout << "foo1 exit" << endl;
    }
     
    int main()
    {
     resource r;
     foo1(r);
     return 0;
    }
    En effet, on pourrait croire que les appels successifs aux différentes foo impliquent une copie du paramètre, or une bonne optimisation générerait un code équivalent à ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main()
    {
     resource r;
     cout << "foo1 enter" << endl;
     cout << "foo2 enter" << endl;
     cout << r << endl;//assume an existing operator<<  for resource
     cout << "foo2 exit" << endl;
     cout << "foo1 exit" << endl;
     return 0;
    }
    Et là il n'y a plus aucune copie de la resource r.

  4. #4
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    Citation Envoyé par camboui Voir le message
    Dans bien des cas c'est un "déplacement" qu'il faudrait faire plutôt qu'une copie. J'ai cru comprendre qu'il aura dans une future version de C++ ce genre de possibilité ("move" avec l'operator &&) mais ce n'est pas encore d'actualité. Et puis je suppose que le code de la SL ne sera pas réécrit en remplaçant les copies des prédicats par des déplacements, le problème restera.
    Je ne pense pas qu'un déplacement soit une bonne idée dans ce cas. On peut d'ailleurs s'en rendre compte dès maintenant grâce aux auto_ptr qui déplace leur ressource lors d'une copie.... et ben ça assert très très vite si on les passe au algo de la STL. Il suffit d'un sort() par exemple.

    Citation Envoyé par camboui Voir le message
    Pour en revenir à mon exemple, j'essaie d'imaginer un moyen d'éviter la contrainte de copie imposée lors d'un passage de paramètre par valeur (comme c'est le cas pour les fonctions de la SL).
    Il me semble que cette description ressemble beaucoup au reference_wrapper de boost (et maintenant du TR1) qui ont justement été inventé pour feinter et passer par référence tout en gardant une apparence de passage par valeur.

    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
     
    #include <array>
    #include <functional>
     
    class BigRessource
    {
    public:
       BigRessource():c(){printf("Ctor BR\n");}
       ~BigRessource(){printf("Dtor BR\n");}
     
    private:
       char c[5000]; // big !
     
       // Pour s'assurer qu'il n'y a vraiment aucune copie...
       BigRessource(const BigRessource& br){};
       BigRessource& operator=(const BigRessource& br){};
    };
     
    struct Pred : public std::binary_function<int, int, bool>
    {
       Pred():br_(){}
       bool operator()(int i1, int i2)
      {
     
       //...
     
        return i1 < i2;
      }
       BigRessource br_;
    };
     
    int main()
    {
       std::array<int, 4> v = {4,  5, 6, 8};
       Pred pred;
       std::sort(v.begin(), v.end(), std::ref(pred));
       return 0;
    }
    C'est très proche de ta solution finalement. Sauf qu'au lieu d'avoir une référence dans le prédicat sur la ressource, on a un pointeur sur le prédicat dans le reference_wrapper, ce qui permet de garder l'écriture du prédicat de manière plus naturelle AHMA.

    HS : L'implémentation de reference_wrapper vaut vraiment le coup d'œil. Pour une fois avec du code venant de boost, c'est étonnamment simple, élégant et court. D'ailleurs la voici dans son intégralité(sans les workaround pour les vieux compilos)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    template<class T> class reference_wrapper
    { 
    public:
        typedef T type;
        explicit reference_wrapper(T& t): t_(&t) {}
        operator T& () const { return *t_; }
        T& get() const { return *t_; }
    private:
        T* t_;
    };
    Un opérateur de conversion vers un T&... c'est magnifique et je savais même pas que le C++ était capable de ça.

  5. #5
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    J'ai failli applaudir comme toi, mais... bouahhhh, snif, mais pourquoi ça compile pas sous VS2005
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    c:\program files\microsoft visual studio\vc98\include\algorithm(633) :
     error C2064: term does not evaluate to a function
    J'ai utilisé <boost/ref.hpp> au lieu de <functional> puisque que ton code est pour VS2008, mais ça devrait fonctionner quand même
    C'est comme s'il ne voyait pas le Pred::operator() via reference_wrapper (qui, lui, n'en a pas).

  6. #6
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    Ah mince.
    Effectivement, j'ai testé std::ref avec vs2010 beta 2 dans laquelle std::reference_wrapper<T> dispose d'un opérateur() qui transmet à l'opérateur() du T sous-jacent. Par contre il semblerait bien que boost::reference_wrapper n'en ait pas, lui.

    Il y a peut être une solution avec boost.bind, mais c'est tout de suite moins sexy... (non testé)
    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
     
     
    class Pred
    {
    public:
       template <typename T>
       bool operator()(const T& t1, const T& t2)
       {
          return t1 < t2;
       }
    private:
       BigRessource br_;
    };
     
    Pred pred;
    std::sort(v.begin(), v.end(), boost::bind<bool>(boost::ref(pred), _1, _2));
     
    // ou
    std::sort(v.begin(), v.end(), boost::bind<bool>(boost::ref(Pred()), _1, _2));

  7. #7
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    C'est fou ça

    Bon, j'ai modifié le header boost/ref.hpp en ajoutant simplement ceci dans reference_wrapper:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    	template<typename V>
    	bool operator()(V const & rl, V const & rr)
    	{
    		return t_->operator()(rl,rr);
    	}
    Et ça marche !

    Mais... Ils testent leur code les gars de chez boost ?!?!

  8. #8
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    Ben oui, mais...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<typename V>
    bool operator()(V const & rl, V const & rr)
    {
       return t_->operator()(rl,rr);
    }
    C'est pas très générique tout ça.
    C'est peut être pour ça qu'ils l'ont pas mis...

    D'ailleurs dans vs2010 l'implémentation générique de l'opérateur()... ouch
    En théorie c'est "relativement" simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
     // template variadique pour avoir un nombre quelconque de paramètre
    template <class... ArgTypes>
    typename result_of<T(ArgTypes...)>::type
    operator() (ArgTypes&&...) const; // + une pincée de perfect forwarding
    Mais en pratique y'a du préprocesseur de partout c'est terrifiant.

  9. #9
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    En tout cas, merci arzar (j'aime ton smiley)

    Je fais faire un petit copier/coller de ce reference_wrapper si simple pour mon petit usage personnel
    C'est un beau moyen pour "convertir" un passage par valeur en référence.

    ...

    Pour en revenir à mon premier post, la troisième écriture de prédicat ne me plaisait guère, bien que fonctionnelle.
    Je vais donc garder la deuxième, bien plus élégante comme tu le disais aussi, avec un petit predicate_wrapper perso directement inspiré de reference_wrapper.
    Mais quand même, pourquoi boost propose-t-il du code inutilisable ?

  10. #10
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    C'est un template variadique ça, non?

    @Camboui: Vue l'orthographe de "BigRessource", je doute que le code avec les foncteurs soit un exemple fourni par boost. Je pense qu'ils n'avaient tout simplement pas prévu un tel usage de reference_wrapper...
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  11. #11
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Pourtant, si je comprends bien la doc de boost c'est fait pour contourner la contrainte de passage par valeur des prédicats, entre autres, des algos de la STL.
    reference_wrapper is primarily used to "feed" references to function templates (algorithms) that take their parameter by value. It provides an implicit conversion to T&, which usually allows the function templates to work on references unmodified.
    Je me demande d'ailleurs ce que ça donnerait de passer les iterator comme ça. De jolis crashs je pense

  12. #12
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    En fait je me suis rendu compte en cherchant la mailing de list de boost que l'ajout d'un opérateur() a été discuté plusieurs fois, par exemple ici

    Pour résumé le lien plus haut, ça n'a jamais vraimment aboutit, apparemment:
    - par flemme
    - car on peut contourner l'absence de l'opérateur assez facilement avec un boost.bind comme je l'ai montré plus haut.
    - parce que l'implémentation de l'opérateur() en C++98 est assez pénible - il faut se cogner du préprocesseur pour émuler le nombre variable de paramètre
    - parce que les astuces de métaprog utilisée pour connaitre le type de retour demanderait d'inclure la moitié de boost (type_traits, MPL, bind...) ce qui chagrine un peu vu qu'à l'heure actuelle reference_wrapper est indépendant de tout le reste.

  13. #13
    Membre chevronné
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Points : 2 205
    Points
    2 205
    Par défaut
    Ouai c'est ça surtout, le fait qu'on peut s'en sortir avec boost.bind, ça évite de sortir toute l'artillerie lourde.
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  14. #14
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Pourtant ça ne parait pas insumontable à faire sans usine à gaz derrière
    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
    template<class T,class B>
    class reference_wrapper
    { 
        T *t_ptr;
    public:
        typedef T type;
     
        explicit reference_wrapper(T & t):t_ptr(&t) {}
        operator T& () const { return *t_ptr; }
    	template<typename V>
    	B operator()(V const & r) const { return (*t_ptr)(r); }
    	template<typename V>
    	B operator()(V const & rl, V const & rr) const { return (*t_ptr)(rl,rr); }
    };
     
    template<class T,class B> inline reference_wrapper<T,B> const ref(T & t)
    { 
        return reference_wrapper<T,B>(t);
    }
     
    template<class T,class B> inline reference_wrapper<T const,B> const cref(T const & t)
    {
        return reference_wrapper<T const,B>(t);
    }
    Testé et vérifié dans certains cas usuels de la STL. Je pense même que c'est bon pour tous les types de prédicats de la STL.

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

Discussions similaires

  1. Optimiser les performances try/catch ?
    Par KiLVaiDeN dans le forum Langage
    Réponses: 4
    Dernier message: 14/01/2014, 13h47
  2. Optimiser les tables mysql, nécessaire ?
    Par Michaël dans le forum Requêtes
    Réponses: 5
    Dernier message: 15/07/2005, 18h11
  3. Optimiser les jointures dans des requêtes
    Par klereth dans le forum PostgreSQL
    Réponses: 12
    Dernier message: 23/04/2005, 17h29
  4. Réponses: 5
    Dernier message: 04/10/2004, 18h20
  5. Optimiser les tables
    Par blizar dans le forum PostgreSQL
    Réponses: 3
    Dernier message: 04/06/2004, 08h34

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