Skip to content

Ideas: exploring ways to reduce compilation time #5294

Description

@nlohmann

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 provide to_json/from_json overloads 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 (around json.hpp is_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) and binary_writer.hpp (~1.9k lines) — a sizeable fraction of the header that every TU compiles even when only text JSON is used. Idea: a JSON_NO_BINARY_FORMATS-style opt-out, analogous to the existing JSON_NO_IO.
  • Caveat that makes this non-trivial: basic_json has friend/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::json specialization, 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions