none
extern template in Visual C++ 2010 RRS feed

  • Frage

  • Basierend auf der Liste der implementierten C++0x-Features (http://blogs.msdn.com/b/vcblog/archive/2010/04/06/c-0x-core-language-features-in-vc10-the-table.aspx) sollte extern template funktionieren. Bei meinen Versuchen mit der Visual C++ 2010 Express Edition hat sich jedoch herausgestellt, dass dies anscheinend nicht der Fall ist.

    Zu Testzwecken habe ich drei Dateien erstellt:

    IntVector.h
    #ifndef INTVECTOR_H
    #define INTVECTOR_H
    #include <vector>
    extern template class std::vector<int>;
    #endif

    UseVector1.cpp
    #include "IntVector.h"
    bool f1(std::vector<int> const& v)
    {
    return v.empty()  && v.size() > 0;
    }

    UseVector2.cpp
    #include "IntVector.h"
    bool f2(std::vector<int> const& v)
    {
    return v.empty()  && v.size() > 0;
    }

    Kompiliere ich ein entsprechendes Projekt, erhalte ich Warnungen C4231 beim Parsen des IntVector-Headers.

    Wenn ich extern template richtig verstehe, sollte das std::vector-Template in IntVector.obj komplett instanziert sein. In den beiden kompilierten Dateien UseVector1.obj und UseVector2.obj sollte kein Code generiert werden, da das Tempalte ja als extern deklariert wurde. GCC 4.4 stützt diese Annahme. Kompiliere ich den Code mit Visual C++ 2010, sind die beiden UseVector-Dateien 19kb groß. Das entspricht genau der Größe, die entsteht, wenn ich in IntVector.cpp die beiden Funktionen explizit instanziere und sie im Header als extern deklariere, deswegen gehe ich davon aus, dass extern template nicht wie beschrieben funktioniert.

    Gehe ich recht in der Annahme, das extern template nur die bereits vorhandene Semantik implementiert, mit der Templates in dlls exportiert werden können? Wenn ja sollte die oben genannte Tabelle unbedingt berichtigt werden. Falls nein würde ich gerne wissen, wo der Fehler liegt. C++0x-Features werden unterstützt, das habe ich durch folgende Funktion überprüft:

    void f()
    {
    auto x = []{return 0;}();
    }

    Donnerstag, 16. Dezember 2010 13:49

Antworten

  • Ok! Meine Schuld.
    Ich habe mal wieder zu wenig geschrieben.

    Die aktuelle Implementierung definiert die Ausführung des extern Keywords so:
    http://msdn.microsoft.com/en-us/library/by56e477.aspx
    The extern keyword in the specialization only applies to member functions defined outside of the body of the class. Functions defined inside the class declaration are considered inline functions and are always instantiated.

    Da aber die Implementierung von vector und den anderen std-Klassen, ales Funktionen im Klassen Körper vorsehen, kann ich nur sehen, dass der Linker den Rest der Arbeit macht.

    Und hier erreicht man auch eine extrem gute Optimierung (auch ohne extern Keyword) durch "Whole Program optimization" (LTCG - Link Time Code Genration). Allerdings steigt die Linker Zeit extrem an, weil erst im Linker der eigentliche Compile abläuft und der optimiert extrem gut.

    Wir haben aber in der Vergangenheit auf /LTCG verzichtet weil uns gute Übertragbarkeit des Codes in PDBs und Crashdumps wichtiger waren. Zudem haben wir eine eher DB lastige Anwednung in der CPU Speed


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Freitag, 17. Dezember 2010 07:20
    Moderator
  • Oh, da fehlt natürlich die IntVector.cpp:

    #include <vector>
    template class std::vector<int>;

    Da ist dann die Implementierung. Ich habe das ganze aber nicht gelinkt sondern nur kompiliert, da der Linker ja sowieso die mehrfach vorhandenen Instanziierungen entfernt. Beim GCC 4.4 erhalte ich dann zwei fast leere Dateien und eine größere IntVector1.o, die offensichtlich die implementierung enthält. Ich habe gerade das Beispiel ein wenig geändert, so dass in den UseVector-Dateien ein std::vector<int> instanziiert wird:

    UseVector1.cpp:

    f1(std::vector<int> const& v)
    {
    std::vector<int> tmp = v;
    std::vector<int> tmp2(v);
    tmp.clear();
    tmp.begin();
    tmp.end();
    tmp.at(0);
    tmp.insert( tmp.end(), 0);
    tmp.reserve(100);
    tmp.resize(10);
    tmp.push_back(0);
    tmp.front();
    tmp.back();
    tmp.pop_back();
    tmp.swap(tmp2);
    tmp.assign(1,0);
    tmp.capacity();
    tmp.get_allocator();
    tmp.max_size();
    tmp.rbegin();
    tmp.rend();
    return v.size() && tmp.empty(); }

    UserVector2.cpp dann entsprechend.

    Kompiliere ich das Projekt dann mit VC++ 2005, erhalte ich folgende Dateigrößen:
    IntVector.obj 273kb
    UseVector1/2.obj 230kb

    Beim GCC erhalte ich die Größen:
    IntVector.o 51kb
    UseVector1/2.o 4kb

    Laut nm sind alle std::vector<int>-Methoden in den UseVector-Objectfiles undefined:

    nm -C -u useIntVector2.o
             U _Unwind_Resume
             U std::vector<int, std::allocator<int> >::size() const
             U std::vector<int, std::allocator<int> >::empty() const
             U std::vector<int, std::allocator<int> >::capacity() const
             U std::vector<int, std::allocator<int> >::max_size() const
             U std::vector<int, std::allocator<int> >::_M_fill_assign(unsigned int,
    int const&)
             U std::vector<int, std::allocator<int> >::at(unsigned int)
             U std::vector<int, std::allocator<int> >::end()
             U std::vector<int, std::allocator<int> >::back()
             U std::vector<int, std::allocator<int> >::rend()
             U std::vector<int, std::allocator<int> >::swap(std::vector<int, std::al
    locator<int> >&)
             U std::vector<int, std::allocator<int> >::begin()
             U std::vector<int, std::allocator<int> >::clear()
             U std::vector<int, std::allocator<int> >::front()
             U std::vector<int, std::allocator<int> >::insert(__gnu_cxx::__normal_it
    erator<int*, std::vector<int, std::allocator<int> > >, int const&)
             U std::vector<int, std::allocator<int> >::rbegin()
             U std::vector<int, std::allocator<int> >::resize(unsigned int, int)
             U std::vector<int, std::allocator<int> >::reserve(unsigned int)
             U std::vector<int, std::allocator<int> >::pop_back()
             U std::vector<int, std::allocator<int> >::push_back(int const&)
             U std::vector<int, std::allocator<int> >::vector(std::vector<int, std::
    allocator<int> > const&)
             U std::vector<int, std::allocator<int> >::~vector()
             U __gxx_personality_v0

     Analog kann man mit dumpbin sehen, dass Visual C++ das Template jedes Mal instanziiert.

    Freitag, 17. Dezember 2010 10:10

Alle Antworten

  • Soweit ich die Implementierung verstehe wird das erst durch den Linker erledigt und der bindet den Code exakt einmal, sofern er nicht sowieso als inline expanded wird. Was evtl. auch die identische Code Größe bedeuten kann.


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Donnerstag, 16. Dezember 2010 20:16
    Moderator
  • Martin – wenn dem so ist, warum sollte Microsoft dann überhaupt extern template eingeführt haben? COMDAT-Folding gibt es im Compiler doch schon ewig und der Geschwindigkeitsvorteil wäre auch dahin. Im Proposal für extern template steht außerdem, dass eine Ausführung im Linker der Status Quo sei den es überhaupt erst zu verbessern gelte.

    jensa79 – ich weiß nicht, inwiefern dein Beispiel aussagekräftig sein soll. Die Definitionen von empty() und size() dürften gegenüber der 19 KiB eigentlich so winzig ausfallen, dass sie im Alignment untergehen. Desweiteren habe ich ein generelles Verständnisproblem damit: das Template ist in beiden Einheiten deklariert (als extern), aber in keiner Einheit tatsächlich definiert. Imo sollte dieses Programm also überhaupt nicht linken können. Wie hast du es denn mit GCC 4.4 verifiziert?

    Freitag, 17. Dezember 2010 00:25
  • Ok! Meine Schuld.
    Ich habe mal wieder zu wenig geschrieben.

    Die aktuelle Implementierung definiert die Ausführung des extern Keywords so:
    http://msdn.microsoft.com/en-us/library/by56e477.aspx
    The extern keyword in the specialization only applies to member functions defined outside of the body of the class. Functions defined inside the class declaration are considered inline functions and are always instantiated.

    Da aber die Implementierung von vector und den anderen std-Klassen, ales Funktionen im Klassen Körper vorsehen, kann ich nur sehen, dass der Linker den Rest der Arbeit macht.

    Und hier erreicht man auch eine extrem gute Optimierung (auch ohne extern Keyword) durch "Whole Program optimization" (LTCG - Link Time Code Genration). Allerdings steigt die Linker Zeit extrem an, weil erst im Linker der eigentliche Compile abläuft und der optimiert extrem gut.

    Wir haben aber in der Vergangenheit auf /LTCG verzichtet weil uns gute Übertragbarkeit des Codes in PDBs und Crashdumps wichtiger waren. Zudem haben wir eine eher DB lastige Anwednung in der CPU Speed


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Freitag, 17. Dezember 2010 07:20
    Moderator
  • Ok, das erklärt natürlich warum es mit std::vector<int> nicht funktioniert, weil da alle Methoden in der Klasse definiert werden. Aber was bringt mir dann extern template? In unserem Projekt habe wir sehr hohe turnaround-Zeiten, weil der Linker aus sehr großen Libraries erstmal alle doppelten Symbole entfernen muß. Das ist nicht gerade effizient, und ich hatte gehofft, das Problem dadurch zu entschärfen. Da es aber üblich ist, dass Methoden in der Template-Klasse definiert werden, ist das Feature für mich relativ sinnlos. Es wäre halt praktisch, die immer wieder vorhandenen Instanzen der STL/iostream und aus der Boost-Bibliothek nur einmal zu erzeugen.

    Dass der Linker die entfernt und auch gut optimierten Code erzeugt stimmt, allerdings dauert das selbst ohne Optimierung sehr lange und läßt sich schlecht parallelisieren, da es sehr IO-lastig ist.

    Freitag, 17. Dezember 2010 08:21
  • Oh, da fehlt natürlich die IntVector.cpp:

    #include <vector>
    template class std::vector<int>;

    Da ist dann die Implementierung. Ich habe das ganze aber nicht gelinkt sondern nur kompiliert, da der Linker ja sowieso die mehrfach vorhandenen Instanziierungen entfernt. Beim GCC 4.4 erhalte ich dann zwei fast leere Dateien und eine größere IntVector1.o, die offensichtlich die implementierung enthält. Ich habe gerade das Beispiel ein wenig geändert, so dass in den UseVector-Dateien ein std::vector<int> instanziiert wird:

    UseVector1.cpp:

    f1(std::vector<int> const& v)
    {
    std::vector<int> tmp = v;
    std::vector<int> tmp2(v);
    tmp.clear();
    tmp.begin();
    tmp.end();
    tmp.at(0);
    tmp.insert( tmp.end(), 0);
    tmp.reserve(100);
    tmp.resize(10);
    tmp.push_back(0);
    tmp.front();
    tmp.back();
    tmp.pop_back();
    tmp.swap(tmp2);
    tmp.assign(1,0);
    tmp.capacity();
    tmp.get_allocator();
    tmp.max_size();
    tmp.rbegin();
    tmp.rend();
    return v.size() && tmp.empty(); }

    UserVector2.cpp dann entsprechend.

    Kompiliere ich das Projekt dann mit VC++ 2005, erhalte ich folgende Dateigrößen:
    IntVector.obj 273kb
    UseVector1/2.obj 230kb

    Beim GCC erhalte ich die Größen:
    IntVector.o 51kb
    UseVector1/2.o 4kb

    Laut nm sind alle std::vector<int>-Methoden in den UseVector-Objectfiles undefined:

    nm -C -u useIntVector2.o
             U _Unwind_Resume
             U std::vector<int, std::allocator<int> >::size() const
             U std::vector<int, std::allocator<int> >::empty() const
             U std::vector<int, std::allocator<int> >::capacity() const
             U std::vector<int, std::allocator<int> >::max_size() const
             U std::vector<int, std::allocator<int> >::_M_fill_assign(unsigned int,
    int const&)
             U std::vector<int, std::allocator<int> >::at(unsigned int)
             U std::vector<int, std::allocator<int> >::end()
             U std::vector<int, std::allocator<int> >::back()
             U std::vector<int, std::allocator<int> >::rend()
             U std::vector<int, std::allocator<int> >::swap(std::vector<int, std::al
    locator<int> >&)
             U std::vector<int, std::allocator<int> >::begin()
             U std::vector<int, std::allocator<int> >::clear()
             U std::vector<int, std::allocator<int> >::front()
             U std::vector<int, std::allocator<int> >::insert(__gnu_cxx::__normal_it
    erator<int*, std::vector<int, std::allocator<int> > >, int const&)
             U std::vector<int, std::allocator<int> >::rbegin()
             U std::vector<int, std::allocator<int> >::resize(unsigned int, int)
             U std::vector<int, std::allocator<int> >::reserve(unsigned int)
             U std::vector<int, std::allocator<int> >::pop_back()
             U std::vector<int, std::allocator<int> >::push_back(int const&)
             U std::vector<int, std::allocator<int> >::vector(std::vector<int, std::
    allocator<int> > const&)
             U std::vector<int, std::allocator<int> >::~vector()
             U __gxx_personality_v0

     Analog kann man mit dumpbin sehen, dass Visual C++ das Template jedes Mal instanziiert.

    Freitag, 17. Dezember 2010 10:10