Tuesday 19th May 2015 5.00pm
Tue, May 19, 2015Link shared: https://github.com/sakra/cotire
As part of publicising my C++ Now 2015 talk last week, here is part 15 of 20 from its accompanying Handbook of Examples of Best Practice for C++ 11⁄14 (Boost) libraries:
15. BUILD: Consider defaulting to header only, but actively manage facilities for reducing build times
Making your library header only is incredibly convenient for your users - they simply drop in a copy of your project and get to work, no build system worries. Hence most Boost libraries and many C++ libraries are header only capable, often header only default. A minority are even header only only.
One thing noticed in the library review is just how many of the new C++ 11⁄14 libraries are header only only, and whilst convenient I think library authors should and moreover can do better. For some statistics to put this in perspective, proposed Boost.AFIO v1.3 provides a range of build configurations for its unit tests:
Header only
Precompiled header only (default)
Precompiled not header only (library implementation put into a shared library)
Precompiled header only with link time optimisation
Build flags Microsoft Windows 8.1 x64 with Visual Studio 2013 Ubuntu 14.04 LTS Linux x64 with GCC 4.9 and gold linker Ubuntu 14.04 LTS Linux x64 with clang 3.4 and gold linker
Debug header only 7m17s 12m0s 5m45s
Debug precompiled header only 2m10s 10m26s 5m46s
Debug precompiled not header only 0m55s 3m53s asio failure
Release precompiled header only 2m58s 9m57s 8m10s
Release precompiled not header only 1m10s 3m22s asio failure
Release precompiled header only link time optimisation 7m30s 13m0s 8m11s
These are for a single core 3.9Ghz i7-3770K computer. I think the results speak for themselves, and note that AFIO is only 8k lines with not much metaprogramming.
The approaches for improving build times for your library users are generally as follows, and in order of effect:
1. Offer a non-header only build configuration
Non-header build configurations can offer build time improvements of x4 or more, so these are always the best bang for your buck. Here is how many Boost libraries offer both header only and non-header only build configurations by using something like this in their config.hpp:
// If we are compiling not header only
#if (defined(BOOST_AFIO_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && !defined(BOOST_AFIO_STATIC_LINK)
# if defined(BOOST_AFIO_SOURCE) // If we are compiling the library binary
# undef BOOST_AFIO_HEADERS_ONLY
# define BOOST_AFIO_DECL BOOST_SYMBOL_EXPORT // Mark public symbols as exported from the library binary
# define BOOST_AFIO_BUILD_DLL // Tell code we are building a DLL or shared object
# else
# define BOOST_AFIO_DECL BOOST_SYMBOL_IMPORT // If not compiling the library binary, mark public symbols are imported from the library binary
# endif
#else // If we are compiling header only
# define BOOST_AFIO_DECL // Do no markup of public symbols
#endif // building a shared library
// Configure Boost auto link to get the compiler to auto link your library binary
#if !defined(BOOST_AFIO_SOURCE) && !defined(BOOST_ALL_NO_LIB) && <br /> !defined(BOOST_AFIO_NO_LIB) && !AFIO_STANDALONE && !BOOST_AFIO_HEADERS_ONLY
#define BOOST_LIB_NAME boost_afio
// tell the auto-link code to select a dll when required:
#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_AFIO_DYN_LINK)
#define BOOST_DYN_LINK
#endif
#include <boost/config/auto_link.hpp>
#endif // auto-linking disabled
#if BOOST_AFIO_HEADERS_ONLY == 1 // If AFIO is headers only
# define BOOST_AFIO_HEADERS_ONLY_FUNC_SPEC inline // Mark all functions as inline
# define BOOST_AFIO_HEADERS_ONLY_MEMFUNC_SPEC inline // Mark all member functions as inline
# define BOOST_AFIO_HEADERS_ONLY_VIRTUAL_SPEC inline virtual // Mark all virtual member functions as inline virtual
// GCC gets upset if inline virtual functions aren't defined
# ifdef BOOST_GCC
# define BOOST_AFIO_HEADERS_ONLY_VIRTUAL_UNDEFINED_SPEC { BOOST_AFIO_THROW_FATAL(std::runtime_error("Attempt to call pure virtual member function")); abort(); }
# else
# define BOOST_AFIO_HEADERS_ONLY_VIRTUAL_UNDEFINED_SPEC =0;
# endif
#else // If AFIO is not headers only
# define BOOST_AFIO_HEADERS_ONLY_FUNC_SPEC extern BOOST_AFIO_DECL // Mark all functions as extern dllimport/dllexport
# define BOOST_AFIO_HEADERS_ONLY_MEMFUNC_SPEC // Mark all member functions with nothing
# define BOOST_AFIO_HEADERS_ONLY_VIRTUAL_SPEC virtual // Mark all virtual member functions as virtual (no inline)
# define BOOST_AFIO_HEADERS_ONLY_VIRTUAL_UNDEFINED_SPEC =0; // Mark all pure virtual member functions with nothing special
#endif
This looks a bit complicated, but isn't really. Generally you will mark up those classes and structs you implement in a .ipp file (this being the file implementing the APIs declared in the header) with BOOST_AFIO_DECL, functions with BOOST_AFIO_HEADERS_ONLY_FUNC_SPEC, all out-of-class member functions (i.e. those not implemented inside the class or struct declaration) with BOOST_AFIO_HEADERS_ONLY_MEMFUNC_SPEC, all virtual member functions with BOOST_AFIO_HEADERS_ONLY_VIRTUAL_SPEC and append to all unimplemented virtual member functions BOOST_AFIO_HEADERS_ONLY_VIRTUAL_UNDEFINED_SPEC. This inserts the correct markup to generate both optimal header only and optimal non header only outcomes.
2. Precompiled headers
You probably noticed that in the table above that precompiled headers gain nothing on clang, +13% on GCC and +70% on MSVC. Those percentages vary according to source code, but I have found them fairly similar across my own projects - on MSVC, precompiled headers are a must have on an already much faster compiler than any of the others.
Turning on precompiled headers in Boost.Build is easy:
cpp-pch afio_pch : afio_pch.hpp : <include>. ;
And now simply link your program to afio_pch to enable. If you're on cmake, you definitely should check out https://github.com/sakra/cotire.
3. extern template your templates with their most common template parameters in the headers, and force instantiate those same common instances into a separate static library
The following demonstrates the technique:
// Header.hpp
template<class T> struct Foo
{
T v;
inline Foo(T _v);
};
// Definition must be made outside struct Foo for extern template to have effect
template<class T> inline Foo<T>::Foo(T _v) : v(_v) { }
// Inhibit automatic instantiation of struct Foo for common types
extern template struct Foo<int>;
extern template struct Foo<double>;
// Source.cpp
#include "Header.hpp"
#include <stdio.h>
// Force instantiation of struct Foo with common types. Usually compiled into
// a separate static library as bundling it into the main shared library can
// introduce symbol visibility problems, so it's easier and safer to use a static
// library
template struct Foo<int>;
template struct Foo<double>;
int main(void)
{
Foo<long> a(5); // Works!
Foo<int> b(5); // Symbol not found if not force instantiated above
Foo<double> c(5); // Symbol not found if not force instantiated above
printf("a=%l, b=%d, c=%lf\n", a.v, b.v, c.v);
return 0;
}
The idea behind this is to tell the compiler to not instantiate common instantiations of your template types on demand for every single compiland as you will explicitly instantiate them exactly once elsewhere. This can give quite a bump to build times for template heavy libraries.
Presentation slides: https://goo.gl/VHrXrj