After bringing down my full rebuild time from 30 seconds to 2 seconds in my compiler,
I thought it useful to note down some learned lessons.
Low risk changes
Keep code out of headers
Avoid inline/static functions in headers.
Prefer forward declarations over includes
If you dont absolutely need the full definition of a type then prefer forwarding it.
In C++ you dont need the definition of a type to pass it by reference.
No relative include paths
Either provide the compiler with the include paths properly or reoganize your headers to not need this.
Often an antipattern anyway.
More involved changes
Seperate templates into headers and inline files
Often you'll only need the definition of a template in a header file, and not the full type.
If you declare the template type as you would usually,
but then implement the functions in a seperate file you can avoid alot of include overhead.
// vector.hpp
// old: all in one file
template <typename T> class Vector {
T *m_front;
T *m_back;
T *m_capacity;
public:
T& get(int index) { return *(m_front + index); }
// rest of the impl...
};
// vector.hpp
// new: only the declarations
template <typename T> class Vector {
T *m_front;
T *m_back;
T *m_capacity;
public:
T& get(int index);
// rest of the function declarations
};
// vector.inl
template<typename T>
T& Vector<T>::get(int index) { return *(m_front + index); }
This way you can include just the `.hpp` file in your headers, then the `.inl`
in your source files where you actually need to instantiate those functions.
The stuff that wont make it past code review
Rely on transitive header includes
In wysiwyg style includes you'll often pull in the same headers across multiple files.
- system.h. uses vectors, strings, and core
- vector.h
- string.h
- core.h
- game.h. uses system, vectors, and core
- system.h
- vector.h
- core.h
Relying on transitive deps avoids the compiler needing to query to fs as often.
pragma once isnt free, especially with relative includes.
- system.h. uses vectors, strings, and core
- vector.h
- string.h
- core.h
- game.h. uses system, vectors, and core
- system.h pulls in vector.h and core.h so we dont include them ourselves
Dont declare anything except mandatory dependencies as exports in your build system
Often times your modules of code will provide a few related apis, most other components will not commonly use a modules entire api surface.
Constructing an object may require an allocator, but using it may only require a map.
This means that a module that only ever constructs this type only needs to depend on arenas being built.
And a module that only ever uses it only needs to depend on maps being built.
Not declaring all possibly used modules as dependencies in your build system allows builds and links to happen sooner.
Final results
This graph represents 2 seconds wall clock time on a 5950x, building 180 source files totalling 12000 significant lines of C.
Tools to diagnose build bottlenecks
See the code
The compiler