11

I am using the glm library, which is a header-only collection of math utilities intended for 3D graphics. By using -ftime-trace on Clang and ClangBuildAnalyzer, I've noticed that a lot of time is being spent instantiating glm types:

**** Templates that took longest to instantiate:
 16872 ms: glm::vec<4, signed char, glm::packed_highp> (78 times, avg 216 ms)
 15675 ms: glm::vec<4, unsigned char, glm::packed_highp> (78 times, avg 200 ms)
 15578 ms: glm::vec<4, float, glm::packed_highp> (78 times, avg 199 ms)

...

So, I decided to create a wrapper header/source pair for glm, and use extern template to avoid unnecessary instantiations:

// glmwrapper.h

#pragma once

#include <glm.hpp>

extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp

template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;

Now, in my project, instead of including <glm.hpp>, I include "glmwrapper.h" instead. Unfortunately, that did not change anything. Using -ftime-trace and ClangBuildAnalyzer again reports the same number of instantiations. There also is no measurable compilation time difference.

I suspect that this is because #include <glm.hpp> does actually end up including the template definition, and at that point the subsequent extern template declarations are just redundant.

Is there a way to achieve what I want without modifying the glm library?


In pseudocode, I kinda want something like this:

// glmwrapper.h (psuedocode)

#pragma once

#include <glm.hpp>

// Make definition of the templates unavailable:
undefine template struct glm::vec<4, signed char, glm::packed_highp>;
undefine template struct glm::vec<4, unsigned char, glm::packed_highp>;
undefine template struct glm::vec<4, float, glm::packed_highp>;

// Make declaration of the templates available:
extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp (psuedocode)

// Define templates only in the `.cpp`, not in the header:
template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    This is not a duplicate of [this](https://stackoverflow.com/questions/8130602/using-extern-template-c11) nor of [this](https://stackoverflow.com/questions/41872632/explicit-instantiation-declaration-of-header-only-templateextern-template) as neither solve my problem in a way that is suitable for me. – Vittorio Romeo Apr 28 '20 at 10:00
  • Curious, I would've thought your initial approach would elide the various instantiations, even if the definitions are exposed prior to the explicit instantiation (re-)declarations, as I would assume the (header only) lib itself should not not lead to any instantiations. Could it be that [\[dcl.spec.auto\]/14](https://timsong-cpp.github.io/cppwp/n4659/dcl.spec.auto#14) applies here, and that these extra instantiations are not for accessing the (re-instantiated) definitions, but for `auto` type deduction? Either in the lib itself (due to other instantiations) or where you make use of it. – dfrib Apr 28 '20 at 10:18
  • ... Or, more general, could [\[temp.explicit\]/10](https://timsong-cpp.github.io/cppwp/n4659/temp.explicit#10) be in effect for entities in `S` in _"Except for [entity in `S`], explicit instantiation declarations have the effect of suppressing the implicit instantiation of the entity to which they refer."_? – dfrib Apr 28 '20 at 10:25
  • @dfri: Even if I remove all the code from `glmwrapper.cpp`, the project still compiles without any warning or error. I would expect at least some of the usages of `glm::vecX` to fail due missing definition. Either my suspicion is correct, or something else is wrong here... – Vittorio Romeo Apr 28 '20 at 10:32
  • 1
    I may be wrong, but would removing the explicit instantiation definitions for `glmwrapper.cpp` not be UB, no diagnostic required? From [\[temp.explicit\]/11](https://timsong-cpp.github.io/cppwp/n4659/temp.explicit#11): _"An entity that is the subject of an explicit instantiation declaration and that is also used in a way that would otherwise cause an implicit instantiation in the translation unit shall be the subject of an explicit instantiation definition somewhere in the program; otherwise the program is ill-formed, no diagnostic required."_. – dfrib Apr 28 '20 at 10:44
  • I know this is not the answer to your question, but did you consider recommandation of section 1_2 or 1_3 first ? it is stated explicitly : Note: Including and is convenient but pull a lot of code which will significantly increase build time, particularly if these files are included in all source files. We may prefer to use the approaches describe in the two following sections to keep the project build fast. https://github.com/g-truc/glm/blob/master/manual.md#section1_2 – sandwood Apr 28 '20 at 12:51
  • Is it possible that most of the instantiations happen in the glm header already? Or are there explicit specializations for these choices of parameters? Because in both cases the explicit instantiation declaration wouldn't have any effect. – Corristo Apr 30 '20 at 18:35
  • May [this](https://stackoverflow.com/questions/56844624/how-does-glm-get-away-with-not-declaring-a-function-inline-and-defining-it-inlin) be related to your suspect about the template definitions? – Bob__ May 03 '20 at 09:11
  • Explicit instantiation definitions do not prevent instantiating class templates; if they did, you wouldn’t be able to use any members of the resulting class. They merely prevent **emitting** all the member functions, which may or may not be expensive compared to the instantiation. – Davis Herring May 12 '20 at 19:47
  • @DavisHerring: I do not understand. I do not expect *explicit instantiation definitions* to prevent instantiation, I expect them to do the opposite: instantiate a template right where I want and when I want. If you meant `extern template`, why *"I wouldn't be able to use any member of the resulting class"* if I had a corresponding *explicit instantiation definition*? – Vittorio Romeo May 12 '20 at 20:53
  • 1
    @VittorioRomeo: Yes, I meant the accompanying explicit instantiation declarations, sorry. You wouldn’t be able to use the members in a translation unit that had access only to the explicit instantiation declaration (*if* that prevented instantiation) because the class would be incomplete. – Davis Herring May 12 '20 at 21:06
  • @DavisHerring: I feel like I am missing something fundamental. The way I would use `extern template` is right after the definition of a template in a header. E.g. `template struct Vector2D { /* ... */ }; extern template Vector2D; extern template Vector2D;`. I would then provide an accompanying `.cpp` containing explicit instantiation definitions for the `int` and `float` versions of `Vector2D`. I would expect users of my class to link against that `.cpp`, and I would expect uses of `Vector2D` (or `float`) to absolutely not instantiate anything. Am I wrong? – Vittorio Romeo May 12 '20 at 21:20
  • 2
    @VittorioRomeo: That is how you use it, but you are wrong about the effect: clients who need `Vector2D` to be complete must instantiate the class template *despite* the `extern template struct …` because they can’t use the `.o` file (during compilation, not linking!) to learn what the members and bases are. They don’t have to *generate code* for the non-inline member functions of the class template, but they do have to have their declarations (in the usual as-needed fashion). – Davis Herring May 12 '20 at 21:42
  • @DavisHerring: that makes sense now, thanks. I will gladly award you my bounty if you put that in an answer and relate it to my initial question :) – Vittorio Romeo May 12 '20 at 22:03
  • @VittorioRomeo: I finally looked at your “non-duplicates”; while your question and my answer are more detailed and more informative, this *is* just about exactly the same as the second one in substance. – Davis Herring May 13 '20 at 03:50

1 Answers1

4

Unfortunately, there’s no way to avoid these instantiations. An explicit instantiation declaration of a class template doesn’t prevent (implicit) instantiation of that template; it merely prevents instantiating its non-inline, non-template member functions (which is often none of them!) because some other translation unit will supply the actual function symbols and object code.

It’s not that seeing the template definition causes instantiation (which specialization would be instantiated?). The reason is that code which requires that the class be complete still needs to know its layout and member function declarations (for overload resolution), and in general there’s no way to know those short of instantiating the class:

template<class T> struct A : T::B {
  typename std::conditional<sizeof(T)<8,long,short>::type first;
  typename T::X second;
  A() noexcept(T::y)=default;  // perhaps deleted
  using T::B::foo;
  void foo(T);
  // and so on…
};

void f() {A<C> a; a.foo(a.first);}  // …maybe?

This “transparency” extends to several other kinds of templated entities as well: if compilation needs the definition of a template, the symbols generated for the linker are irrelevant.

The good news is that C++20’s modules should help with situations like this: an explicit instantiation definition in a module interface will cause a typical implementation to cache the instantiated class definition with the rest of the module interface data, avoiding both parsing and instantiation in importing translation units. Modules also remove the implicit inline on class members and friends defined in the class (which hasn’t meant much in a long time anyway), increasing the number (or, put differently, the convenience) of functions for which explicit instantiation declarations do prevent implicit instantiation.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Is [cppreference](https://en.cppreference.com/w/cpp/language/class_template) inaccurate then: *"An explicit instantiation declaration (an extern template) skips implicit instantiation step: the code that would otherwise cause an implicit instantiation instead uses the explicit instantiation definition provided elsewhere (resulting in link errors if no such instantiation exists). This can be used to reduce compilation times by explicitly declaring a template instantiation in all but one of the source files using it, and explicitly defining it in the remaining file.*"? – Vittorio Romeo May 13 '20 at 21:30
  • Also, what does this mean for code bloat? I am failing to understand how `extern template` can ever be useful, if all it does is *"merely prevent instantiating its non-inline, non-template member functions"*. – Vittorio Romeo May 13 '20 at 21:32
  • @VittorioRomeo: It’s useful for **function** (and variable) templates, which can be called/used without instantiating them. This extends to non-inline member functions (of which there are more in modules); member function templates can of course be explicitly instantiated *themselves* (with additional template arguments, of course) as well. Remember that class template member functions are individually instantiated only if used, which reduces bloat. Cppreference isn’t *wrong*, but it’s leaving a lot implied. – Davis Herring May 13 '20 at 22:12