I'm trying to optimize my build time using extern templates because I have a lot of generated headers that contain typedefs to a template class.
The template class
// TypeID.h
template <typename T>
class TypeID
{
public:
TypeID(/* <some parameters> */);
bool isNull() const;
// ... many other methods
};
template <typename T>
TypeID<T>::TypeID(/* <some parameters> */)
{
// impl
}
template <typename T>
bool TypeID<T>::isNull() const
{
// impl
}
// same for the rest of the methods
Example of generated header
// NamedID.h
#include "TypeID.h"
typedef TypeID</* some type */> NamedID;
There are many (~2k) headers like NamedID with different types and they're included throughout the project.
I changed the code generator to add this line above the typedef:
extern template class TypeID</* some type */>;
and in addition to the header files, it now also generates a cpp where all the extern templates have a corresponding
template class TypeID</* some type */>;
Due to the number of headers and how many times they're used in the project I expected a big difference in compile time (at least something noticeable) but there's no difference.
I ran several runs of the build with and without this change and all of them take 2h 30m +/-2m.
Did I implement this wrong ? Am I expecting too much ?
My environment:
RHEL 7.7
GCC 8.3.1
CMake + ninja, no ccache, no icecream/distcc
Related
I have been dabbling with new C++20 features such as modules and concepts. One of the inherent properties of the new modules is that they do not leak pre-processor definitions to consumers -- this is both a blessing and a curse because some behaviours, such as logging, in C++ have traditionally been implemented with #define macros so that they can be #defined to nothing in release builds.
My question is simple: how should one go about implementing logging without macros, today, assuming that one still wants to retain behaviours like having the compiler entirely remove logging calls, with no side effects, in release builds?
My endeavour to achieve this exploits lambdas and C++20 concepts to drive template specialization.
#define NOOP /* no operation */
template <typename T>
concept printable = requires (const T & message) {
std::cout << message;
};
template <typename F>
concept format_factory = std::regular_invocable<F>
&& std::convertible_to<std::invoke_result_t<F>, std::string_view>;
...
#ifdef _DEBUG
private:
template<std::regular_invocable F>
static inline const std::invoke_result_t<F> map(F f) {
return f();
}
template<typename T>
static inline constexpr const T& map(const T& value) {
return value;
}
public:
template<printable T>
static inline void trace(const T& message) {
std::cout << message << std::endl;
}
template<typename... Args>
static inline void trace(const std::string_view& format, Args&&... args) {
std::cout << std::format(format, map(args)...) << std::endl;
}
template<format_factory F, typename... Args>
static inline void trace(const F& format, Args&&... args) {
std::cout << std::format(format(), map(args)...) << std::endl;
}
#else
public:
template<typename... Args>
static inline constexpr void trace(const Args&... args) { NOOP; }
#endif
The idea is that...
any literals and values to be logged can be passed, normally, because, in release builds, the compiler will optimise out any copies or moves since they will not be accessed.
anything 'expensive' to be logged can be passed as an accessor lambda which will not be invoked in release builds and, consequently, also be optimised out.
For example, user-code might look like this:
Log::trace("format literal ({}, {})", []() { return "expensive value"; }, "cheap value");
I have tried this with Visual C++ 2022 (preview) and I can confirm that it does work as intended but is it a good idea? How could I make it better?
Remember that this is done because I want to export this from a C++20 module and I can't do that with preprocessor macros as far as I understand.
Modularized approach
The entire approach with macro-definitions in headers in discouraged with modules. Not that it's impossible, but those headers with logging configuration need to be parsed by the compiler for each source file that include them.
I suppose you could add the macro definitions at the command line, e.g. with gcc:
g++ [...] -DDEBUG_LOGGING
And with Visual C++ (from here):
[...] /p:DefineConstants="DEBUG_LOGGING"
Working with headers
"Remember that this is done because I want to export this from a C++20 module and I can't do that with preprocessor macros as far as I understand."
--> If you want to export the logging functionality as a module, then the implementation cannot depend upon configurations of a header file. In C++20 we have two ways of including headers: Inside the global module fragment and inside the module declaration:
module;
#define DEBUG_LOGGING // or similar configuration
#include "logging.hpp" // inside global module fragment
export module some_module;
// 1)
#define DEBUG_LOGGING
#include "logging.hpp" // inside module declaration
// 2)
#define DEBUG_LOGGING
import logging; // defined is ignored with module unit
Quoting from cppreference:
"#include should not be used in a module unit (outside the global module fragment), because all included declarations and definitions would be considered part of the module. Instead, headers can also be imported with an import declaration: "
And:
"Importing a header file will make accessible all its definitions and declarations. Preprocessor macros are also accessible (because import declarations are recognized by the preprocessor). However, contrary to #include, preprocessing macros defined in the translation unit will not affect the processing of the header file. This may be inconvenient in some cases (some header files uses preprocessing macros as a form of configuration), in which case the usage of global module fragment is needed. "
So the logging header may be included in the global module fragment, but then the compiler needs to process it several times. This is especially troublesome if that header is using a 3rd party library like spdlog or similar, in which case that header-inclusion becomes a recursive nightmare.
Alternatively, the logging header may be included inside the module declaration, but then its definitions will leak outside that module, and the header file may not import any modules at all. That last bit I tried with gcc and got the following error:
log_helper.hpp:3:1: error: post-module-declaration imports must not be from header inclusion
Code sample
We should not depend on header file configuration to declare (and parse) our templated logging functions since that is a horribly slow process, so instead we can feed the configurations to the compiler statically (by instantiating a type). The general idea is to compile the module once, and depend on consumers of the module to generate the appropriate function calls.
Here is the code that you suggested:
template<format_factory F, typename... Args>
static inline void trace(const F& format, Args&&... args) {
std::cout << std::format(format(), map(args)...) << std::endl;
}
If we call it with trace ("arg={}", my_obj); then the compiler will generate code for that particular overload. However, if we then call it with a different number of arguments, e.g. trace ("arg_1={}, arg_2={}", my_obj1, my_obj2);, then the compiler will have to lex and parse the trace function again (including recursing through dependent templated types), which is a slow process. With a module, we compile once and store that as an abstract syntax tree in binary format, so generating code after that will be much faster.
This is my suggestion for a code sample. I have omitted variadic template expansion to ease reading:
// logging.cpp
export module logging;
export class ILogger
{
public:
virtual void Trace() = 0;
};
export enum class LogType
{
null,
default
};
class NullLogger : public ILogger
{
public:
void Trace() override {};
};
export class Log
{
public:
static void Configure(LogType lt) {
switch(lt) {
case LogType::null:
mInstance = std::make_shared<NullLogger>();
return;
default:
break;
}
}
static void Trace() { mInstance->Trace(); }
private:
std::shared_ptr<ILogger> mInstance;
};
I will have to take a look at optimized assembly (link-time optimizations), to check if the indirection for null logger is optimized away. Likely, some more experienced C++ folks can tell immediately.
An idea for optimization would be to store store a function pointer for mInstance->Trace() to avoid vtable lookups. But again, the compiler might deduce that automatically. I have very limited experience with that particular subject.
Our project uses boost::serialization to serialize many things.
But some types are not correctly registered and when serializing them we get an "unregistered class" error
I have narrowed the problem to the BOOST_CLASS_EXPORT_KEY, which, for some types are not generating code.
What BOOST_CLASS_EXPORT_KEY does is :
namespace boost {
namespace serialization {
template<>
struct guid_defined< T > : boost::mpl::true_ {};
template<>
inline const char * guid< T >(){
return K;
}
} /* serialization */
} /* boost */
All objects that are serialized inherit from a base class called Serializable.
Serialization is always done via a pointer to the base class.
This works fine except for one case:
There is a class template SerializableList which is a Serializable which holds a list of T
template< typename T>
class SerializableList
{
...
std::vector<T> m_list;
template<class Archive>
void serialize( Archive & ar, const unsigned int /*version*/ )
{
ar & boost::serialization::base_object<businessObjects::Serializable>(*this);
ar & mList;
}
};
in a dedicated cpp and hpp files we then declare each instantiation of this template to boost serialization like this:
hpp:
BOOST_CLASS_EXPORT_KEY( SerializableList<SomeT*> );
BOOST_CLASS_EXPORT_KEY( SerializableList<SomeOtherT*> );
BOOST_CLASS_EXPORT_KEY( SerializableList<AThirdT*> );
cpp:
BOOST_CLASS_EXPORT_IMPLEMENT( SerializableList<SomeT*> );
BOOST_CLASS_EXPORT_IMPLEMENT( SerializableList<SomeOtherT*> );
BOOST_CLASS_EXPORT_IMPLEMENT( SerializableList<AThirdT*> );
But half of these lines do not produce executable code in the final executable! if we put a breakpoint on each of those lines and run, half the breakpoints disappear, those who stay are on the working types (those we can serialize).
For instance the breakpoints would stay on SerializableList<SomeT*> and SerializableList<AThirdT*> but not SerializableList<SomeOtherT*>.
Btw, we have also tried to call directly boost::serialization::guid<T>(), and while it works fine for say:
boost::serialization::guid<SerializableList<SomeT*> >() which returns the key,
it doesn't for
boost::serialization::guid<SerializableList<SomeOtherT*> >() which calls the default implementation ...
So is there a compiler bug (we use Visual C++ 2010 SP1), or some good reason for the compiler to ignore some of those specializations?
I forgot to mention, all this code lies in a library, which is linked against the exe project. I've tried with different exe projects and sometimes it works sometimes it doesn't ... the compilation options are the same... I really have no clue what's going on :'(
We found the solution,
One (serializable) class had several SerializableList members, and did not include the file with all the "BOOST_CLASS_EXPORT_KEY" lines.
the other projects which were working didn't use that particular class ...
I am a new bee to template based programming and I have this error when compiling my project
error : multiple definition of Expr::Chapter_2<double>::get_pointer()
objectfile.o:/Filename.h:42 first defined here.
The given code is entirely inside a .h header file. Any pointers to resolve this issue is highly appreciated.
code :
template< typename T >
class Chapter_2{
-------
public :
inline T* get_pointer();
-------
};
// Function definitions
template< typename T >
T* Chapter_2<T>::get_pointer() {
------code------
}
// double specialization of template
template<>
double* Chapter_2<double>::get_pointer() {
------code------
}
Possible reasons:
1)In case You have your header not starting with #ifndef and you have included the same header twice or more.
2) In a cpp file where you are including the header containing the template, if you have again defined methods ( multiple definitions i.e., in cpp as well as in .h). The implementation of the methods in the template should be defined at the same place once.
I'm designing a CUDA-C++ library with template classes. There are template functions my classes use, and they are invisible to main as well as the user. I need to specialize them explicitly because of the two steps of compiling to be performed, otherwise I'd get an "unresolved external" error when linking. Being this classes used in main.cpp, there's no way (I guess...) to tell nvcc what types are going to be used in tha main program, so I thought of using some macros to specialize them. Here's a simplified versione of the code:
//CUDA_functions.h
// CUDA functions declared here and included in files that will be compiled
// with g++. Those functions are implemented in .cu files, compiled with nvcc
template <typename T>
void foo1(T x);
template <typename T>
void foo2(T x);
template <typename T>
void foo3(T x);
//fileA.h - included in main.cpp
#include "CUDA_functions.h"
template <typename T>
class A {
// it uses foo1 & foo2 inside
}
//fileB.h - included in main.cpp
#include "CUDA_functions.h"
template <typename T>
class B {
// it uses foo1 & foo3 inside
}
//macros.h
#define _USE_CLASS_A(T) template void foo1(T); \
template void foo2(T); /**/
#define _USE_CLASS_B(T) template void foo1(T); \
template void foo3(T); /**/
//user_spec.cu - template specializations by user. This is the first file to be
// - compiled and it doesn't know what classes are going to be used
// say, user wants to use classes A & B: HERE THE ERROR RAISES!
#include "macros.h"
_USE_CLASS_A( int );
_USE_CLASS_B( int );
When I compile this code with Visual Studio, I get a warning about the double explicit instantiation (foo1), but when I compile it with g++ warning becomes an error!
I can't write macros like
#define _USE_FOO1(T) template void foo1(T) /**/
#define _USE_FOO2(T) template void foo2(T) /**/
#define _USE_FOO3(T) template void foo3(T) /**/
because the user doesn't have to worry about the existence of those functions and I'd like to specialize a list of them based on what class he/she is going to use. Last but not least, I found nothing about a "conditional specialization" of template. What can I do to solve? Thanks to everyone would be so nice to answer. Bye.
Is it for host code or device code? I believe CUDA does not support linking for device code. Linking template functions in host code has always been a bit fishy, CUDA or no CUDA.
Instead of having your hands dirty with macros -- how about putting them in a header, inside of namespace detail?
By convention, detail namespace indicates library internal stuff that you shouldn't ever access as a user.
Been away from C++ for a few years and am getting a linker error from the following code:
Gene.h
#ifndef GENE_H_INCLUDED
#define GENE_H_INCLUDED
template <typename T>
class Gene {
public:
T getValue();
void setValue(T value);
void setRange(T min, T max);
private:
T value;
T minValue;
T maxValue;
};
#endif // GENE_H_INCLUDED
Gene.cpp
#include "Gene.h"
template <typename T>
T Gene<T>::getValue() {
return this->value;
}
template <typename T>
void Gene<T>::setValue(T value) {
if(value >= this->minValue && value <= this->minValue) {
this->value = value;
}
}
template <typename T>
void Gene<T>::setRange(T min, T max) {
this->minValue = min;
this->maxValue = max;
}
Using Code::Blocks and GCC if it matters to anyone. Also, clearly porting some GA stuff to C++ for fun and practice.
The template definition (the cpp file in your code) has to be included prior to instantiating a given template class, so you either have to include function definitions in the header, or #include the cpp file prior to using the class (or do explicit instantiations if you have a limited number of them).
Including the cpp file containing the implementations of the template class functions works. However, IMHO, this is weird and awkward. There must surely be a slicker way of doing this?
If you have only a few different instances to create, and know them beforehand, then you can use "explicit instantiation"
This works something like this:
At the top of gene.cpp add the following lines
template class Gene<int>;
template class Gene<float>;
In if(value >= this->minValue && value <= this->minValue) the second minValue should be maxValue, no?
Echo what Sean said: What's the error message? You've defined and declared the functions, but you've not used them in anything anywhere, nor do I see an error (besides the typo).
TLDR
It seems that you need an Explicit Instantiation i.e. to actually create the class. Since template classes are just "instructions" on how to create a class you actually need to tell the compiler to create the class. Otherwise the linker won't find anything when it goes looking.
The thorough explanation
When compiling your code g++ goes through a number of steps the problem you're seeing occurs in the Linking step. Template classes define how classes "should" be created, they're literally templates. During compile time g++ compiles each cpp file individually so the compiler sees your template on how to create a class but no instructions on what "classes" to create. Therefore ignores it. Later during the linking step the g++ attempts to link the file containing the class (the one that doesn't exist) and fails to find it ultimately returning an error.
To remedy this you actually need to "explicitly instantiate" the class by adding the following lines to Gene.cpp after the definition of the class
template class Gene<whatever_type_u_wanna_use_t>;int
Check out these docs I found them to be super helpful.