Share a precompiled header across the object libraries - #8673
Open
glebm wants to merge 1 commit into
Open
Conversation
glebm
force-pushed
the
compile-time
branch
4 times, most recently
from
August 23, 2026 16:43
c575132 to
5a15def
Compare
glebm
marked this pull request as ready for review
August 23, 2026 18:05
Header parsing dominates our build: about 78% of the time spent compiling Source/ goes into headers rather than into our own code, spread evenly rather than concentrated in a few files. Precompile the third-party headers that nearly every translation unit pulls in. Measured with ccache disabled, this takes the full build from 1092s to 840s of CPU (-23%) for a single 2.8s PCH build. pch.hpp deliberately contains no devilutionX headers. Including our hot project headers as well was measured to save only a few percent more, and would mean every edit to engine/ or utils/ invalidated the PCH and rebuilt ~250 translation units. The PCH is owned by libdevilutionx_pch and reused by every other object library. That target is kept free of the optional third-party defines (asio/sol/mpqfs) that only some libraries get: GCC accepts a PCH whose macro state is a subset of the consumer's but rejects a superset, so reusing libdevilutionx's own PCH fails for most of them. Executables are excluded because they build -fPIE rather than -fPIC, which GCC also treats as invalidating. Sharing one PCH this way relies on the compiler tolerating a subset of the consumer's flags, which CMake documents as unsupported and only GCC and Clang are lenient about. MSVC reports a mismatch as an error rather than falling back to parsing the header, so DEVILUTIONX_PCH_SHARED is off there and libdevilutionx gets its own PCH instead. That keeps most of the benefit: it is ~150 of the ~250 affected sources, and all of them already compile with identical flags. The PCH target also needs asio's include directories. GCC does not check include paths when validating a precompiled header, so a difference there is silent: the PCH bakes in one resolution of a header and the consumer can no longer reach the other. 3DS and Switch shadow <errno.h> with a shim on asio's include path to add ESHUTDOWN, which their newlib only defines under __LINUX_ERRNO_EXTENSIONS__, and pch.hpp reaches <errno.h> via <ostream>. Only the include directories: linking asio would add its compile definitions to the PCH and consumers without them would then reject it. USE_SDL1 and USE_SDL3 move to DEVILUTIONX_DEFINITIONS for the same reason, in the other direction: the PCH links DevilutionX::SDL and is built with the macro, and taking the include directories alone is not an option because pch.hpp needs the macro to decide which SDL header to include. Defining it everywhere instead of only where SDL is linked keeps the PCH valid for the 16 leaf libraries that do not link SDL. Over their 17 sources a rejected PCH measures 22.4s against 14.2s without one, and it is silent: a target that was never given a PCH is not compiled with -Winvalid-pch either. Compiling them with the macro produces byte-identical objects, so nothing but PCH validity changes. DevilutionX::SDL still defines it for the targets that are not built through add_devilutionx_library(), such as the tests. SDL2 defines no such macro and was never affected. The precompiled header is C++, so sources in other languages are marked SKIP_PRECOMPILE_HEADERS. Amiga adds C sources to libdevilutionx and iOS adds an Objective-C one, and without this CMake looks for a C precompiled header that no target builds and fails to generate. The sources that raise the optimization level to stay usable in Debug builds opt out too. A precompiled header is only valid for a translation unit compiled at the same optimization level: GCC ignores it with a -Winvalid-pch warning, Clang fails the build outright. This gives up ~3.3s in optimized builds, where the levels do match, but SKIP_PRECOMPILE_HEADERS is evaluated at generate time and so cannot be made per-configuration. The third-party includes are __has_include-guarded. The owning target always has them available, so the precompiled image is complete; the guards only matter if a toolchain ever rejects the PCH, so that the fallback re-parse degrades to a slower build rather than a broken one in a target that lacks those include directories. ccache needs pch_defines,time_macros sloppiness to hash a PCH at all, and it has to reach ccache at build time, hence the launcher wrapper. Without it ccache misses on every PCH-using compile. Set DEVILUTIONX_PCH=OFF to disable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
glebm
enabled auto-merge (rebase)
August 24, 2026 10:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a shared precompiled header to use with all the translation units. This reduces compilation time by about 20% across the board, measured locally and evident from CI build times here.