Ideas: exploring ways to reduce compilation time
Status: brainstorming, not a plan
This is a collection of ideas for reducing compile-time overhead, written up so they are not lost. Nothing here is a sanctioned TODO or a commitment. Each item is a hypothesis to investigate, measure, and discuss before anything is decided. Some may turn out to be not worth it, break API/ABI, or conflict with the single-header philosophy. Treat this as a starting point for discussion.
Context / measurements
Some quick local measurements (single GCC machine, single-header, best of 3; absolute numbers are illustrative, not authoritative — they will differ per compiler/machine):
| Scenario | C++11 | C++17 | C++20 |
|---|---|---|---|
#include <nlohmann/json.hpp> + empty main |
~0.84s | ~1.18s | ~1.69s |
| …of which the ~44 pulled-in STL headers alone | ~0.44s | ~0.66s | ~1.20s |
| Full parse + dump usage | ~2.9s | ~3.2s | — |
Two observations worth keeping in mind when weighing ideas:
- There seem to be two separable costs: a fixed per-TU include cost (paid by any TU that includes the header) and a template-instantiation cost (paid per TU that actually uses conversions/parse/serialize, roughly a quarter of a usage build in
-ftime-report). - A large share of the include cost appears to come from the standard-library headers the header pulls in, not from our own code — and that share grows sharply at C++20.
Ideas (each unproven; needs measurement + discussion)
A. Trim/guard unconditional STL includes
<valarray>and<forward_list>currently appear to be included unconditionally, solely to provideto_json/from_jsonoverloads for those niche container types (detail/conversions/from_json.hpp,detail/conversions/to_json.hpp). Idea: guard those overloads (and their includes) behind an opt-in macro. Question to answer: how much does this actually save, and is silently dropping those overloads acceptable / how to make it discoverable?<any>seems to be pulled in for a single trait usage (aroundjson.hppis_same<ValueType, std::any>). Idea: investigate whether that check can be expressed without including<any>.- The C++20-heavy includes (
<format>,<ranges>,<any>) already look guarded behind feature macros — so likely little left to reclaim there, and the C++20 blow-up may be largely intrinsic. Worth confirming rather than assuming.
B. Optionally excluding the binary formats
- CBOR / MessagePack / BSON / UBJSON live in
binary_reader.hpp(~3.1k lines) andbinary_writer.hpp(~1.9k lines) — a sizeable fraction of the header that every TU compiles even when only text JSON is used. Idea: aJSON_NO_BINARY_FORMATS-style opt-out, analogous to the existingJSON_NO_IO. - Caveat that makes this non-trivial:
basic_jsonhasfriend/member-typedef coupling to those classes, so member functions would need guarding too, not just the includes. Needs a careful design sketch before it's even clear this is worth pursuing.
C. Better surface the existing json_fwd.hpp
- A forward-declaration header already exists but may be under-advertised. Idea: documentation/tutorial showing how to keep the full header out of widely-included headers (declare interfaces via
json_fwd.hpp, include the full header only in.cpps). This is likely mostly a docs question rather than a code change, and could help the common "the header leaked into a common header and now recompiles everywhere" complaint.
D. Optional precompiled / extern-template path
- For the common default
nlohmann::jsonspecialization, idea: offer explicit instantiation declarations plus a single compiled TU, so the template-instantiation cost is paid once rather than per-TU (the "firewall" pattern people currently hand-roll). Open questions: maintenance burden, how it interacts with the header-only promise, whether it's opt-in only.
E. Longer-term: C++20 modules
- Idea / research direction only: a module interface could in principle remove much of the repeated header-parse cost. Realistically a large undertaking with fragile toolchain support today — noting it as a direction, not a near-term option.
Framing
Realistic goal, if any of this is pursued, would be materially lower fixed overhead, not parity with minimal/parser-focused libraries — that trade-off is inherent to being a single ergonomic header. Again: these are ideas to evaluate, not decisions.
Source: nlohmann/json