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 :

[Débat] Conventions pour réduire le temps de compilation des templates


Sujet :

C++

  1. #1
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut [Débat] Conventions pour réduire le temps de compilation des templates
    Admettons qu'un certain projet utilise beaucoup de modèles (alias templates) et mette beaucoup de temps à compiler en partie à cause de cela.
    Le projet contient des entêtes qui contiennent à chaque fois la définition des modèles qu'il déclare.

    Pour réduire le temps de compilation, quand une unité de compilation utilise une instance de modèle de fonction (par exemple foo<int>()) ou une fonction d'une instance d'un modèle de classe (par exemple Toto<int>::bar()) :
    -On aimerait éviter d'instancier la définition de foo<int>() ou de Toto<int>::bar() (ce que permet extern template) si cette dernière est déjà instanciée dans une autre unité de compilation.
    -Alors, on aimerait éviter que cette unité de compilation inclut des choses qui ne servent qu'à compiler foo<int>() ou Toto<int>::bar().

    Je propose la procédure suivante :

    Étape 1 : Tripler des entêtes

    L'entête "NomFichier.h" devient les 3 entêtes suivants :
    • NomFichier_light.h
      Permet de déclarer un ou plusieurs modèles, sans forcément les définir. Néanmoins, pour la même raison qu'une classe non template a parfois des fonctions en ligne, "NomFichier_light.h" peut avoir quelques définitions.
    • NomFichier.h
      Permet d'inclure toutes les définitions du ou des modèles déclarés directement dans "NomFichier_light.h", ainsi que les définitions des éventuels autres modèles qu'ils utilisent : "NomFichier.h" inclut parfois "AutreNomFichier.h" mais jamais "AutreNomFichier_light.h".
    • NomFichier_timpl.h (timpl comme template implementation)
      Permet d'inclure toutes les définitions du ou des modèles déclarés directement dans "NomFichier_light.h". Néanmoins, quand c'est possible, on évite d'inclure les définitions des éventuels autres modèles qu'ils utilisent : "NomFichier_timpl.h" préfère inclure "AutreNomFichier_light.h" à "AutreNomFichier.h".


    Remarque : Pour certains projets, on pourrait supprimer "NomFichier_timpl.h" pour simplifier la procédure. Les entêtes seraient alors seulement doublés.

    Étape 2 : Isoler des instanciations dans des unités de compilation spécifiques

    Par exemple, admettons qu'un fichier "Zoo.cpp" utilise le modèle de classe Toto<int> :
    • On crée le fichier "Toto_tinst__int.cpp" qui instancie Toto<int> (tinst comme template instantiation).
    • Dans "Zoo.cpp", on remplace #include "Toto.h" par #include "Toto_light.h" et on ajoute un commentaire pour dire qu'il faut inclure au projet "Toto_tinst__int.cpp".
    • "Toto_tinst__int.cpp" contient soit #include "Toto.h", soit, quand cela permet de réduire le temps de compilation, #include "Toto_timpl.h". Dans le 2e cas, il faut ajouter un commentaire dans "Toto_tinst__int.cpp" pour signaler quel(s) autre(s) fichier(s) ".cpp" il faut inclure au projet pour que ça compile.


    Exemple de résultat de la procédure :

    Foo est une classe très simple qui sera utilisée plus loin.
    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
    //////////
    // Foo.h :
     
    #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_H
    #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_H
     
    #include <iosfwd>
     
    class Foo
    {
    };
     
    std::ostream&  operator<<(std::ostream&  os, const Foo& foo);
    std::wostream& operator<<(std::wostream& os, const Foo& foo);
     
    #endif
     
    ////////////
    // Foo.cpp
     
    #include "Foo.h"
    #include <ostream>
     
    std::ostream&  operator<<(std::ostream&  os, const Foo& foo) { os <<  "ASCII foo\n"; return os; }
    std::wostream& operator<<(std::wostream& os, const Foo& foo) { os << L"wide  foo\n"; return os; }
    Utils :
    • Utils est un espace de nom qui contient une fonction normale et un modèle de fonction print.
    • Il n'y a pas de distinction entre "Utils.h" et "Utils_timpl.h".
    • "Utils_tinst__charAndVectorFoo.cpp" instancie la définition de Utils::print<char, std::vector<Foo>>.

    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
    ////////////////
    // Utils_light.h
     
    #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_UTILS_LIGHT_H
    #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_UTILS_LIGHT_H
     
    #include <iosfwd>
     
    namespace Utils
    {
    	void nonTemplateFunction();
     
    	template<class CharT, class ContainerT>
    	void print(std::basic_ostream<CharT>& os, const ContainerT& container);
    }
     
    #endif
     
    ////////////
    // Utils.cpp
     
    #include "Utils_light.h"
     
    namespace Utils
    {
    	void nonTemplateFunction()
    	{
    	    // code...
    	}
    }
     
    ////////////////
    // Utils_timpl.h
     
    #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_UTILS_TIMPL_H
    #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_UTILS_TIMPL_H
     
    #include "Utils_light.h"
    #include <ostream>
     
    namespace Utils
    {
    	template<class CharT, class ContainerT>
    	void print(std::basic_ostream<CharT>& os, const ContainerT& container)
    	{
    		for(const auto& element : container)
    			os << element;
    	}
    }
     
    #endif
     
    //////////
    // Utils.h
     
    #include "Utils_timpl.h"
     
    ////////////////////////////////////
    // Utils_tinst__charAndVectorFoo.cpp
     
    #include "Utils.h"
    #include "Foo.h"
    #include <vector>
     
    template void Utils::print<char, std::vector<Foo>>(
        std::ostream& os, const std::vector<Foo>& container);
    FooContainerPrinter :
    • FooContainerPrinter est un modèle de classe qui utilise Foo et Utils.
    • Il y a une distinction entre "FooContainerPrinter.h" et "FooContainerPrinter_timpl.h" : "FooContainerPrinter.h" inclut "Utils.h". La conséquence est illustrée dans les deux points suivants.
    • "FooContainerPrinter_tinst__charAndVector.cpp" inclut "FooContainerPrinter_timpl.h" mais a besoin que le projet contienne "Utils_tinst__charAndVectorFoo.cpp" qui instancie la définition de Utils::print<char, std::vector<Foo>>.
    • "FooContainerPrinter_tinst__wchar_tAndDeque.cpp" inclut "FooContainerPrinter.h" qui est plus lourd que "FooContainerPrinter_timpl.h", mais c'est nécessaire car il faut instancier la définition de Utils::print<wchar_t, std::deque<Foo>> et aucun autre ".cpp" le fait.


    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
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    //////////////////////////////
    // FooContainerPrinter_light.h
     
    #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_CONTAINER_PRINTER_LIGHT_H
    #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_CONTAINER_PRINTER_LIGHT_H
     
    #include <iosfwd>
    #include <type_traits>
     
    class Foo;
     
    template<class CharT>
    class FooContainerPrinter
    {
    public:
    	inline explicit FooContainerPrinter(std::basic_ostream<CharT>& os) : m_os(os) {}
     
    	template<class ContainerT>
    	inline void print(const ContainerT& container) const
    	{
    		static_assert(std::is_same<Foo, typename ContainerT::value_type>::value,
    		              "The container argument must be a container of Foo.");
    		printImpl(container);
    	}
     
    	// Inline implicit functions:
    	// -destructor
    	// -copy constructor
    	// -move constructor
     
    private:
    	template<class ContainerT>
    	void printImpl(const ContainerT& container) const;
     
    	std::basic_ostream<CharT>& m_os;
    };
     
    #endif
     
    //////////////////////////////
    // FooContainerPrinter_timpl.h
     
    #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_CONTAINER_PRINTER_TIMPL_H
    #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_CONTAINER_PRINTER_TIMPL_H
     
    #include "FooContainerPrinter_light.h"
    #include "Utils_light.h"
     
    template<class CharT>
    template<class ContainerT>
    void FooContainerPrinter<CharT>::printImpl(const ContainerT& container) const
    {
    	Utils::print(m_os, container);
    }
     
    #endif
     
    ////////////////////////
    // FooContainerPrinter.h
     
    #include "FooContainerPrinter_timpl.h"
    #include "Utils.h"
     
    ///////////////////////////////////////////////
    // FooContainerPrinter_tinst__charAndVector.cpp
     
    // Remark: "Utils_tinst__charAndVectorFoo.cpp" must be added to the project.
     
    #include "FooContainerPrinter_timpl.h"
    #include "Foo.h"
    #include <vector>
     
    template class FooContainerPrinter<char>;
    template void FooContainerPrinter<char>::printImpl<std::vector<Foo>>(const std::vector<Foo>& container) const;
    template void FooContainerPrinter<char>::print    <std::vector<Foo>>(const std::vector<Foo>& container) const;
     
    /////////////////////////////////////////////////
    // FooContainerPrinter_tinst__wchar_tAndDeque.cpp
     
    #include "FooContainerPrinter.h"
    #include "Foo.h"
    #include <deque>
     
    template class FooContainerPrinter<wchar_t>;
    template void FooContainerPrinter<wchar_t>::printImpl<std::deque<Foo>>(const std::deque<Foo>& container) const;
    template void FooContainerPrinter<wchar_t>::print    <std::deque<Foo>>(const std::deque<Foo>& container) const;
    Exemple d'utilisation :
    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
    /////////////////////////
    // WithLightInclusion.cpp
     
    // Remark: "FooContainerPrinter_tinst__charAndVector.cpp"   must be added to the project.
    // Remark: "FooContainerPrinter_tinst__wchar_tAndDeque.cpp" must be added to the project.
     
    #include "Foo.h"
    #include "FooContainerPrinter_light.h"
    #include <deque>
    #include <iostream>
    #include <vector>
     
    namespace WithLightInclusion
    {
    	void doSomething()
    	{
    		std::cout << "WithLightInclusion::doSomething() :\n";
     
    		std::vector<Foo> vectorFoo(2);
    		FooContainerPrinter<char> fooChar(std::cout);
    		fooChar.print(vectorFoo);
     
    		std::deque<Foo> dequeFoo(3);
    		FooContainerPrinter<wchar_t> fooWchar_t(std::wcout);
    		fooWchar_t.print(dequeFoo);
    	}
    };
    On garde la rétrocompatibilité avec la méthode consistant à inclure "FooContainerPrinter.h" sans instancier les définitions de FooContainerPrinter::print dans d'autres ".cpp" :
    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
    /////////////////////////
    // WithUsualInclusion.cpp
     
    #include "Foo.h"
    #include "FooContainerPrinter.h"
    #include <array>
    #include <iostream>
    #include <list>
     
    namespace WithUsualInclusion
    {
    	void doSomething()
    	{
    		std::cout << "WithUsualInclusion::doSomething() :\n";
     
    		std::list<Foo> listFoo;
    		listFoo.push_back(Foo());
    		listFoo.push_back(Foo());
    		FooContainerPrinter<char> fooChar(std::cout);
    		fooChar.print(listFoo);
     
    		std::array<Foo, 3> arrayFoo;
    		FooContainerPrinter<wchar_t> fooWchar_t(std::wcout);
    		fooWchar_t.print(arrayFoo);
    	}
    };
    Remarque à propos des extern template

    Au lieu de tripler les entêtes, on aurait pu utiliser les extern template pour éviter d'instancier plusieurs fois une même définition avec les mêmes types, mais :
    • Ce ne sont pas les extern template qui permettront d'inclure des entêtes n'ayant que le strict minimum.
    • Les extern template empêchent l'inlining, contrairement à la procédure que je propose dans laquelle on peut laisser quelques définitions dans "NomFichier_light.h".
    • Les ".cpp" utilisant les extern template auraient été plus lourds à écrire et à maintenir : voir exemple ci-dessous.


    Voici ce que ça donne avec mon 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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    /////////////////////////
    // WithExternTemplate.cpp
     
    // Remark: "FooContainerPrinter_tinst__charAndVector.cpp"   must be added to the project.
    // Remark: "FooContainerPrinter_tinst__wchar_tAndDeque.cpp" must be added to the project.
     
    #include "Foo.h"
    #include "FooContainerPrinter.h"
    #include <deque>
    #include <iostream>
    #include <vector>
     
    extern template void Utils::print<char, std::vector<Foo>>(
        std::ostream& os, const std::vector<Foo>& container);
     
    extern template class FooContainerPrinter<char>;
    extern template void FooContainerPrinter<char>::printImpl<std::vector<Foo>>(const std::vector<Foo>& container) const;
    extern template void FooContainerPrinter<char>::print    <std::vector<Foo>>(const std::vector<Foo>& container) const;
     
    extern template class FooContainerPrinter<wchar_t>;
    extern template void FooContainerPrinter<wchar_t>::printImpl<std::deque<Foo>>(const std::deque<Foo>& container) const;
    extern template void FooContainerPrinter<wchar_t>::print    <std::deque<Foo>>(const std::deque<Foo>& container) const;
     
    namespace WithExternTemplate
    {
    	void doSomething()
    	{
    		std::cout << "WithExternTemplate::doSomething() :\n";
     
    		std::vector<Foo> vectorFoo(2);
    		FooContainerPrinter<char> fooChar(std::cout);
    		fooChar.print(vectorFoo);
     
    		std::deque<Foo> dequeFoo(3);
    		FooContainerPrinter<wchar_t> fooWchar_t(std::wcout);
    		fooWchar_t.print(dequeFoo);
    	}
    };
    Bilan

    La procédure que je propose devrait réduire le temps de compilation pour les modèles qui ont des définitions lourdes qui sont instanciées avec les mêmes types dans plein d'unités de compilation du même projet.
    Mais cette procédure me semble assez lourde à mettre en place et rendra le code plus difficile à comprendre (donc plus difficile à maintenir) pour les développeurs ne connaissant pas cette procédure.
    Donc, à mon avis, ce serait à utiliser en dernier recours, après les autres méthodes pour réduire le temps de compilation (remplacer des #include par des déclarations en avance, utiliser pimpl...).

    Auriez-vous des améliorations à suggérer pour cette procédure ?

  2. #2
    Membre éclairé Avatar de Matthieu76
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Mars 2013
    Messages
    568
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

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

    Informations forums :
    Inscription : Mars 2013
    Messages : 568
    Points : 890
    Points
    890
    Par défaut réponse rapide
    Bonjour ce que tu dis est très intéressant mais je ne m'y connais pas beaucoup sur ce sujet.

    Moi pour mes templates je passe la valeur en paramètre de ma fonction et c'est tout mais bon c'est juste pour 1 ou 2 truc dans mon code, rien de bien méchant.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <typename T>
    bool reach_data(string symbol_of_test, T value, T value2 = (T)0, bool is_reset = false);
    Par contre, j'aimerais bien que tu m'expliques en quoi mettre les templates dans un fichier à part réduirait le temps de compilation ?
    En faite je vois pas trop comment ta méthode réduira le nombre d'instance ... Faut toujours une instance par "objet", non ?



    PS: Sinon, tu ne peux pas simplement surdéfinir tes fonctions utilisant les templates pour chaque type et appeler la fonction template dans ta surdéfinition ?

  3. #3
    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
    J'ai peur que ta solution ait un soucis qui empêche sa généralisation (outre une certaine lourdeur) : Tu utilises à un moment l'instanciation explicite de templates, qui ne marche pas dans un certain nombre de cas, puisqu’elle essaye d'instancier des fonctions qui ne sont pas toujours instanciables (l'instanciation explicite de std::list<complex> essaierait d'instancier std::list<complex>::sort).
    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.

  4. #4
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    @JolyLoic :

    Dans le cas où on ne peut pas instancier directement toutes les fonctions d'un modèle de classe pour un type donné, on est obligé d'instancier une par une les fonctions que l'on veut utiliser pour ce type :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    typedef std::complex<double> Type;
    template std::list<Type>::list(const std::allocator<Type>& alloc = std::allocator<Type>());
    template void std::list<Type>::push_back(const Type& value);
    // ...
    Donc, ça reste possible. Mais, s'il y a beaucoup de fonctions, c'est vrai que c'est extrêmement lourd.

    @Matthieu76 :

    Citation Envoyé par Matthieu76 Voir le message
    Par contre, j'aimerais bien que tu m'expliques en quoi mettre les templates dans un fichier à part réduirait le temps de compilation ?
    En faite je vois pas trop comment ta méthode réduira le nombre d'instance ... Faut toujours une instance par "objet", non ?
    En fait, je ne parle pas d'instance de classe (une instance de classe = 1 objet), mais d'instance de définition.

    En gros :

    Admettons que l'on ait les fichiers suivants :
    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
    //////////////////////////////
    // Dans Fourmi.h (version 1) :
     
    #include "Fourmiliere.h" // sert juste à compiler Fourmi::travailler()
     
    class Fourmi
    {
    public:
        void travailler()
        {
            // 50 lignes de code
        }
    };
     
    ////////////////////
    // Dans Nature.cpp :
     
    #include "Fourmi.h"
    #include <vector>
     
    namespace Nature
    {
        void faireTravailler(std::vector<Fourmi>& vecFourmis)
        {
            for(Fourmi& fourmi : vecFourmis)
                fourmi.travailler();
        }
    }
    Avec un compilateur normal, les fichier ".cpp" sont compilés indépendamment les uns des autres, avant l'édition des liens.
    Admettons que la compilation de chaque ".cpp" soit un fichier ".o" (l'extension dépend du compilateur).
    Comme "Nature.cpp" ne sait pas s'il existe un autre ".o" qui contient déjà la définition de Fourmi::travailler(), le compilateur va générer le code de Fourmi::travailler() dans "Nature.o".

    Par contre, si on a ceci :
    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
    //////////////////////////////
    // Dans Fourmi.h (version 2) :
     
    class Fourmi
    {
    public:
        void travailler();
    };
     
    ////////////////////
    // Dans Fourmi.cpp :
     
    #include "Fourmi.h"
    #include "Fourmiliere.h" // sert à compiler Fourmi::travailler()
     
    void Fourmi::travailler()
    {
        // 50 lignes de code
    }
     
    ////////////////////
    // Dans Nature.cpp :
     
    #include "Fourmi.h"
    #include <vector>
     
    namespace Nature
    {
        void faireTravailler(std::vector<Fourmi>& vecFourmis)
        {
            for(Fourmi& fourmi : vecFourmis)
                fourmi.travailler();
        }
    }
    "Nature.cpp" inclut "Fourmi.h" qui déclare la fonction Fourmi::travailler() sans la définir. Le compilateur en déduit que le code de Fourmi::travailler() est déjà instancié ailleurs. Plus tard, c'est le lieur qui cherchera où est définie cette fonction et il la trouvera dans "Fourmi.o".
    En plus, "Nature.cpp" ne perd plus de temps à inclure indirectement le code de "Fourmiliere.h" comme avant.

    Dans le cas des modèles (alias templates), par contre, habituellement, on ne sépare pas les déclarations des définitions : on met tout ensemble dans le même fichier comme dans la version 1 de "Fourmi.h" et on se retrouve avec les mêmes inconvénients.

    Alors, j'ai proposé une séparation des fichiers.
    Si on transforme Fourmi et Fourmiliere en modèles de classe, du côté de ModeleFourmi, on a les fichiers suivants :
    • ModeleFourmi_light.h
      C'est l'équivalent de la version 2 de "Fourmi.h" : on n'a pas toutes les définitions de ce que l'on déclare directement.
    • ModeleFourmi_timpl.h
      C'est l'équivalent de "Fourmi.cpp" : on définit tout ce qui est déclaré dans "Fourmi.h". Mais "ModeleFourmi_timpl.h" n'est pas directement compilable en fichier objet ("ModeleFourmi_timpl.o" ne peut être créé).
    • ModeleFourmi.h
      C'est similaire à "Fourmi.cpp", sauf qu'on inclut carrément un fichier similaire à "Fourmiliere.cpp" ("ModeleFourmiliere.h") au lieu de simplement inclure l'équivalent de "Fourmiliere.h" ("ModeleFourmiliere_light.h").
    • ModeleFourmi_tinst__argumentsDuModele.cpp
      Sert à instancier le modèle ModeleFourmi, avec certains arguments, dans un fichier objet ("ModeleFourmi_tinst__argumentsDuModele.o").


    Citation Envoyé par Matthieu76 Voir le message
    PS: Sinon, tu ne peux pas simplement surdéfinir tes fonctions utilisant les templates pour chaque type et appeler la fonction template dans ta surdéfinition ?
    Comme ça ?
    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
    namespace Utils
    {
    	template<class CharT, class ContainerT>
    	void print(std::basic_ostream<CharT>& os, const ContainerT& container)
    	{
    		for(const auto& element : container)
    			os << element;
    	}
    }
     
    inline void UtilsPrint(std::ostream& os, const std::vector<Foo>& container)
    {
    	Utils::print(os, container);
    }
     
    inline void UtilsPrint(std::wostream& os, const std::deque<Foo>& container)
    {
    	Utils::print(os, container);
    }
    Je peux, mais je ne vois pas l'intérêt.

  5. #5
    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
    Je me demandais dans quelle mesure l'utilisation des modules pouvait améliorer la situation quand le temps de compilation semble grandement impacté par les templates.

    En gros, je vois 3 points où ils pourraient améliorer la situation (mais ce ne sont que des hypothèses):
    - Le fait que même si on doit toujours instancier plusieurs fois, au moins on n'est plus obligé de lexer/parser plusieurs fois
    - Le fait que quand une instanciation est dans un .h, si on la déplace dans un module, on devrait ne plus avoir à la réinstancier sans cesse
    - Le fait que quand un template est dans un module, deux instanciations avec les mêmes paramètres depuis deux compilation units différentes sont identiques (ce qui n'étais pas le cas avec les.h), et donc les instanciations peuvent êtres mises en cache

    Comme tu as visiblement du code à disposition où ce temps d’instanciation est assez gênant pour que tu aies envie de faire les pirouettes que tu nous décris là, est-ce que tu as essayé de jouer un peu avec les modules pour voir si la problématique était la même ?
    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.

  6. #6
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Ce serait aussi intéressant de comparer par rapport aux en-têtes pré-compilées ou avec un wrapper de compilateur comme ccache.

  7. #7
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    Comme tu as visiblement du code à disposition où ce temps d’instanciation est assez gênant pour que tu aies envie de faire les pirouettes que tu nous décris là, est-ce que tu as essayé de jouer un peu avec les modules pour voir si la problématique était la même ?
    En fait, à la base, j'avais un ensemble de classes que j'avais hésité à templatiser, pour que l'utilisateur ait le choix du type de caractère (mettre des TCHAR partout n'étant pas suffisant). Ces classes étaient prévues pour un ensemble d'applications dont certaines sont déjà lentes à compiler. Finalement, je m'étais dit qu'il valait mieux ne commencer à les templatiser que le jour où quelqu'un en aurait besoin, si ce jour arrive. Mais, si, ce jour là, cela aurait allongé le temps de compilation des applications qui l'utiliseront déjà, je me suis demandé quelle aurait été la solution de secours.

    Je travaille dans un environnement où il n'y a pas la fonctionnalité des modules comme dans Clang.

  8. #8
    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
    Pour info, les modules de clang ne correspondent pas aux dernières versions discutées dans la norme. De ce point de vue, c'est visual C++ qui est le plus à jour (même si c'est encore sous un flag expérimental).
    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.

  9. #9
    Membre éclairé Avatar de Matthieu76
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Mars 2013
    Messages
    568
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

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

    Informations forums :
    Inscription : Mars 2013
    Messages : 568
    Points : 890
    Points
    890
    Par défaut
    Merci d'avoir prit le temps de me répondre.

    Par contre je ne suis pas expert en c++ et je ne comprends pas cela:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    #include "Fourmiliere.h" // sert juste à compiler Fourmi::travailler()
    Je ne vois pas ce que tu veux dire par compiler Fourmi::travailler(), moi dans mon code je définie ma fonction dans mon .h et j'écrit le code dans mon .cpp et c'est tout. Je savais pas qu'on pouvais écrire du code pour compiler une fonction.
    Pour moi compiler ça revient juste à appuyer sur le bouton Nom : run-button.png
Affichages : 212
Taille : 1,4 Ko

    Visiblement j'ai encore beaucoup à apprendre

  10. #10
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Citation Envoyé par Matthieu76 Voir le message
    et je ne comprends pas cela:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    #include "Fourmiliere.h" // sert juste à compiler Fourmi::travailler()
    Par exemple, si Fourmi::travailler() appelle des fonctions de la classe Fourmiliere, pour que le compilateur puisse compiler le code de Fourmi::travailler(), il faut que les fonctions appelées de Fourmiliere aient été déclarées avant le code de Fourmi::travailler(). Le moyen le plus simple de le faire est d'inclure "Fourmiliere.h".

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 629
    Points : 10 554
    Points
    10 554
    Par défaut
    Citation Envoyé par Matthieu76 Voir le message
    Je savais pas qu'on pouvais écrire du code pour compiler une fonction.
    Réfléchis 2 minutes ce n'est pas compliqué

    Un truc qu'on ne peut pas faire en C++, c'est de déclarer une classe dans plusieurs entêtes .h.

    Par exemple tu as une classe qui représente un groupe d'écrans IHM et tu voudrais faire "main_wnd_user.h" "main_wnd_admin.h" "main_wnd_settings.h" ..., mais c'est la même classe. Juste que tu veux catégoriser cette grosse classe.

    Par contre ce que tu peux faire, c'est une grosse entête avec tout dedans (*) (tu es obligé), et définir tes méthodes/ fonctions/ ... dans plusieurs sources .cpp.
    Pour reprendre mon exemple, tu aurais "main_wnd.h" et "main_wnd_user.cpp" "main_wnd_admin.cpp" "main_wnd_settings.cpp" ... (<- chaque source incluant l'entête)

    Et ensuite, c'est le truc classique du compilateur: une source déjà compilée et non modifiée ne sera pas recompiler. Tu gagnes du temps, en recompilant que "des petits morceaux".


    * -> C'est une technique en C, et apparemment Pyramidev l'utilise avec les "templates"
    Lorsque tu as un ensemble de déclarations (classes, fonctions, ...), tu peux faire plusieurs entêtes en séparer toutes ces déclarations en plusieurs groupes (comme les étages d'une fusée).
    Par exemple: tu fais une entête01 avec une partie des déclarations.
    Tu fais une entête02 qui inclue l'entête01 et rajoute des déclarations.
    Et ainsi de suite.

    Je l'utilise en C pour faire du public/ privé
    Tu mets dans une entête "public_XXX.h" toutes les déclarations publiques. C'est cette entête qui va être "donnée au client".
    Mais tes sources .cpp vont inclure une entête "private_XXX.h" .
    Cette entête inclue "public_XXX.h" et rajoute toutes les déclarations que tu veux masquer au client.

  12. #12
    Membre éclairé Avatar de Matthieu76
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Mars 2013
    Messages
    568
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

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

    Informations forums :
    Inscription : Mars 2013
    Messages : 568
    Points : 890
    Points
    890
    Par défaut re
    Merci pour ton explication, mais moi pour faire "main_wnd_user.h" "main_wnd_admin.h" "main_wnd_settings.h" je fais tout simplement de l'héritage, c'est 20 fois plus simple et c'est prévu pour non ?

    Attends .... Ah d'accord, nan c'est bon j'ai compris En faite c'est juste que dans mon code j'ai pas de fichiers assez gros pour les partitionner.


    Mais pourquoi pas juste faire :

    main_wnd.h:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class main_wnd
    {
        void foo1();
        void foo2();
        void foo3();
    }
    main_wnd.h:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    #include "main_wnd.h"
    main_wnd::foo1()
    {
        // 
    }
    main_wnd_user.cpp:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    #include "main_wnd.h"
    main_wnd::foo2()
    {
        // 
    }
    main_wnd_admin.cpp:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    #include "main_wnd.h"
    main_wnd::foo3()
    {
        // 
    }
    Ah oui, on crée des .h pour chaque fichiers .cpp peut-être pour savoir quels fonctions est dans quels fichiers, c'est ça ?

    En faite comme je suis encore étudiant et que j'apprends en autodidacte, ce genre de chose c'est le genre de chose que je ne peux pas connaître.
    En tout cas merci j'ai l'impression d'avoir beaucoup appris même si c'est pas grand-chose.

  13. #13
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Hello Matthieu76

    Le "pourquoi pas faire :" que tu suggères est exactement ce que foetus explique. Par contre, il ne faut pas utiliser l'héritage pour ça. L'héritage est un choix d'implémentation lourd de conséquences, donc, pattern lettre/enveloppe mis à part, il ne devrait pas être utilisé pour des raisons de facilité de compilation.

    Perso je n'ai jamais eu à casser l'implémentation d'un seul header en plusieurs unités de compilations. Mais je peux comprendre que le besoin apparaisse dans certains projets. En revanche, le ralentissement occasionné par les templates est souvent très frustrant. En particulier avec certaines libs (boost spirit, m'entends-tu ? ).

    Seulement voilà, à mon sens, ce n'est pas au programmeur de structurer son code pour que les templates compilent plus vite. Dans la pratique, nous y sommes forcés, mais c'est un travail que les outils devraient faire, pas les programmeurs. Hélas, on n'a pas encore trouvé de solution efficace au problème.
    Find me on github

  14. #14
    Membre éclairé Avatar de Matthieu76
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Mars 2013
    Messages
    568
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

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

    Informations forums :
    Inscription : Mars 2013
    Messages : 568
    Points : 890
    Points
    890
    Par défaut
    Je rigole si tu te fais chier à faire tout ça et qu'en faite c++17 solutionne tout
    Aprèsje dis ça mais j'ai pas regardé ce que c++17 apportera.

  15. #15
    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
    Les modules, qui sont la meilleure chance d'améliorer ça, ne sont pas pour C++17, mais pour plus tard.
    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.

Discussions similaires

  1. Réponses: 6
    Dernier message: 10/11/2014, 16h23
  2. Réduire le temps de chargement des fichiers sons
    Par j-jorge dans le forum Audio
    Réponses: 6
    Dernier message: 08/10/2013, 19h41
  3. Réponses: 6
    Dernier message: 11/03/2009, 11h26
  4. Optimiser la mémoire pour réduire le temp d'import
    Par Bourak dans le forum Administration
    Réponses: 20
    Dernier message: 03/11/2008, 10h23

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