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++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    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 : 50
    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
    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 éclairé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    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 Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    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 éclairé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    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 Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    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));

+ 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