Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

549 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

psychicstd - a C++ standard library implementation

This is a highly experimental C++ standard library optimized for compilation speed. It is intended to be used during general C++ development to speed up the edit-compile-debug cycle. It is not at all intended to be used for shipping binaries.

It is not complete. It is not fully compliant. But it is good enough to quickly iterate on code. Here are some real world projects that compile and pass their unit tests with psychicstd. The number in the second column indicates the speedup relative to the platform standard library for the compilation phase (1x means same speed, higher is better):

Project Compile time speedup comment
Abseil 2.14x Builds absl/base and eight small upstream base tests.
Bitcoin Core 2.22x Builds all 16 crypto primitives plus memory cleansing, including MuHash and SipHash.
Boost.Asio 2.11x
Boost.Test 1.57x Builds and runs three upstream header-only smoke tests.
catch2 3.51x
cmake 3.06x Uses a compiler wrapper to build.
cppcheck 2.02x
CTRE 1.11x Builds every upstream compile-time test.
eigen 1.89x
Electron 3.52x Builds and tests a focused slice of Electron's startup and command-line handling without a Chromium checkout.
FlatBuffers 2.33x Builds the compiler, library, samples, and C++ test suite.
fmt 1.61x
Godot Engine 1.09x Builds the core static library with SCons; rendering, window-system, audio, and optional engine modules are disabled.
googletest 1.56x
libcamera 4.45x Builds the core libraries and UVC pipeline handler; hardware-dependent applications, optional integrations, bindings, and tests are disabled.
llama.cpp 1.54x Builds ggml-base and ggml-cpu and compiles model-architecture and hyperparameter handling; excludes accelerator backends, tools, examples, server, and tests.
nlohmann json 2.00x Uncovered a reliance on implementation-specific behaviour, fixed in PR #5236.
cxxopts 2.67x Compiles the upstream example with cxxopts' documented no-regex fallback.
inipp 4.66x Compiles the upstream header smoke test.
OpenCV 1.83x Builds the core and imgproc modules and their tests.
pocketfft 1.54x Compiles and runs the upstream demonstration.
pybind11 2.38x Builds pybind11's upstream CMake extension test, imports the module in Python, and calls its bound C++ function.
rapidjson 1.24x Not using much of the standard library, little speedup expected.
rdfind 4.30x Runs in psychic strict mode, see "Compatibility levels" further down this document. Strict mode uncovered code relying on transitive includes.
simdutf 1.70x Mostly SIMD intrinsics. Strict mode uncovered missing includes.
SSVStart 4.21x Compiles the upstream input-utility test against SSVStart's current header dependencies.
strong_type 3.54x Builds and runs the complete upstream self-test suite.
Tesseract 2.18x Builds the OCR library and command-line program using the system Leptonica package.
TensorFlow 2.36x Builds a focused set of core platform/base libraries; excludes Python, CUDA, kernels, and the full framework.
Trompeloeil 1.59x Builds the complete self-test suite and runs the coroutine, threaded, and custom-mutex tests.
Zancle 2.30x Builds the complete Base/System static library.
wordcounter 3.70x Example application using the STL. Counts word occurrences in text files.

The real-world project recipes and reports live in use_on_realworld_projects/. Focused compile-time and compiler-memory measurements are in compile_time.md, with separate process-startup results for Linux and macOS.

Static linking also avoids the libstdc++.so.6 and libm.so.6 dependencies. A representative Linux program measured 1.65x faster exec-to-exit, including loading, initialization, and its small fixed workload.

Once you have coded for a while, switch to a real quality standard library (typically libstdc++ or libc++) and test and build real releases - psychicstd is just intended for speeding up the development.

How complete is it?

Completeness varies by header; development has been guided by what real-world projects need to compile. To investigate std::string, perhaps the most-used header, I counted its public member function names. It has 45 distinct public basic_string method names, the same count as libstdc++: psychicstd is missing copy but adds C++23's contains. See the std::string completeness case study for details. A present method name does not imply full compliance — see compliance.md for behavioral coverage.

Some facilities are omitted deliberately when their practical value does not justify the compile-time cost. For example, <iostream> provides the commonly used narrow streams, but not wcin, wcout, wcerr, or wclog. Defining those rarely used wide streams made a representative program about 6–7% slower to compile even when it did not use them. Such gaps are preferred over making every user pay for unused compliance.

Why?

Slow compilation is one of the pain points of C++. Modules are supposed to help, but they are not ready yet. Even if modules solve slow header parsing, standard libraries are typically optimized for runtime performance. psychicstd does not care about runtime performance — it is all about compilation speed.

I got the idea when I read about the pystd project from Jussi Pakkanen, which has completely different goals but got me thinking.

Writing a standard library is a massive undertaking with lots of corner cases. Three factors in combination make it possible anyway:

  • AI-assisted coding
  • the excellent libc++ test suite
  • no demands on portability, runtime performance, ABI stability etc

An AI can mostly guide itself trying to get the library through all the tests. Anything the AI can generate that passes the test suite is useful.

How does it work?

Psychicstd uses -nostdinc++ -I/path/to/psychicstd/include so the compiler picks up vector, string and other headers from psychicstd instead of libstdc++. A small static library supplies code deliberately moved out of the headers: standard stream objects, cold exception and system-error paths, string conversions, and selected narrow-character template instantiations. Consumers must link this library. The CMake toolchain overlay builds and links it automatically; manual integrations must build and link it explicitly.

The standard library uses std:: namespace just like the real standard. You should not have to do any changes to your program.

Why is it faster?

Compile time for these headers is dominated by the compiler frontend, in two parts: parsing declarations and instantiating templates. psychicstd wins by having far less of both. Raw byte count, number of include files, and backend code generation are all second-order. Precompiled headers help by caching that same frontend work — but they attack the same bottleneck, so psychicstd without a PCH is roughly as fast as libstdc++ with one, and still wins when both use PCH. The case studies measure each of these effects.

A concrete example is that std::sort has a very short and simple implementation to minimize compile time. It is still O(Nlog(N)) but not as fast as other standard libraries in release mode. In debug mode, it can however even be faster!

The compiled-library experiment measures the compile-time and linked-size effects of outlining these paths, explicitly instantiating narrow strings and stringstreams, and splitting the runtime into independently extracted archive members.

Compatibility levels

Real standard library headers pull in a lot of other headers transitively. A lot of real code accidentally relies on that — e.g. using std::equal after only #include <string>, which happens to work because libstdc++'s <string> drags in <algorithm>. That is technically a bug (the code should #include <algorithm>), but it is extremely common.

psychicstd lets you choose how to handle it, via the _PSYCHICSTD_COMPATIBILITY_LEVEL macro:

Level Macro value Behaviour
Drop-in (default) _PSYCHICSTD_COMPAT_DROPIN (2) Mirror libstdc++'s transitive include surface, so unmodified real-world code just compiles. No source changes needed.
Strict _PSYCHICSTD_COMPAT_STRICT (0) Each header includes only what it itself needs. Fastest and smallest — and it doubles as an include-what-you-use checker: code that leaned on a transitive include fails to compile until you add the missing #include (a fix that is also correct under libstdc++/libc++).

Set it like any other define, alongside the other psychicstd flags:

cmake -S . -B build \
    -DCMAKE_CXX_STANDARD=20 \
    -DCMAKE_CXX_FLAGS="-nostdinc++ -isystem /path/to/psychicstd/include -D_PSYCHICSTD_COMPATIBILITY_LEVEL=0"

The default is drop-in, so you get the "just swap it in" experience out of the box; opt into strict when you want maximum speed and to keep your includes honest.

The strict mode results in faster compilation since fewer headers are transitively included. For rdfind, strict mode shrank the compilation time with 5% compared to the drop-in level.

The name

The name is a word play on the edit-compile-debug cycle itself: psychic → cycle → cyclic. A psychic knows the answer before you've finished asking the question. psychicstd tries to do the same — by the time you've hit save, the compiler is already done. Well, that's the aspiration, anyway.

Goals

These are the goals, in order

  • Useful in practice — code should compile, link, and run well enough to support a normal development workflow.
  • Free of UB — projects that are UB free, shall be UB free also when using psychic.
  • Faster compilation — If it is not faster to compile than real implementations like libstdc++, this project has very little value.
  • Sufficiently compliant with the C++ standard — it should be correct enough to be useful. For example, std::string does not need to use small string optimization, which simplifies the implementation.
  • Support C++20

Performance is measured primarily from debug builds of the real-world projects on the current Debian Stable compiler (GCC 14).

Non-goals

  • Runtime performance — we don't try to make your program run faster, only to compile faster. With that said, typical unit tests are expected to run decently fast. On the example projects, psychicstd is sometimes faster on tests!
  • Compilation speed in release mode
  • Portability beyond Linux and macOS. GCC and Clang are supported on Linux; AppleClang is supported on macOS 14.4 and newer. There is no Windows or MSVC support.
  • ABI stability or any kind of guarantees
  • Support older C++ standards

Development guidelines

All code and text should be auto formatted. Use the following:

  • run_clang_format.sh (uses the .clang-format in the git root)
  • run_cmake_format.sh
  • run_markdown_format.sh
  • run_python_lint.sh (formats and checks with ruff)
  • run_shell_format.sh
  • run_yaml_format.sh (formats with yamlfix)

Unit tests should pass. The compliance test need not; 100% compliance is unrealistic.

Use in your project

No changes to your source code are needed. You inject compile and link flags at configure time; your project's own build files stay untouched.

The CMake toolchain described below is the recommended integration and builds the runtime as part of the consuming project. If you inject flags manually, first configure a separate psychicstd build with the same compiler as the consumer. Replace /path/to/your-c++ with that compiler, for example g++-14 or clang++:

cmake -S /path/to/psychicstd -B /path/to/psychicstd/build-runtime \
    -DCMAKE_CXX_COMPILER=/path/to/your-c++ \
    -DPSYCHICSTD_BUILD_TESTS=OFF \
    -DPSYCHICSTD_BUILD_BENCHMARKS=OFF
cmake --build /path/to/psychicstd/build-runtime --target psychicstd

With a single-configuration Makefiles or Ninja generator, the manual examples below call that output /path/to/psychicstd/build-runtime/libpsychicstd.a. Rebuild it when changing compiler or instrumentation flags that should also apply to the runtime.

CMake / GCC 12

GCC 12 lacks -nostdlib++, so you need -nodefaultlibs and all the libraries spelled out:

cmake -S . -B build-with-psychic \
    -DCMAKE_CXX_STANDARD=20 \
    -DCMAKE_CXX_COMPILER_WORKS=1 \
    -DCMAKE_CXX_FLAGS="-nostdinc++ -fvisibility=hidden -isystem /path/to/psychicstd/include" \
    -DCMAKE_EXE_LINKER_FLAGS="-nodefaultlibs" \
    -DCMAKE_CXX_STANDARD_LIBRARIES="/path/to/psychicstd/build-runtime/libpsychicstd.a -lsupc++ -latomic -lm -lc -lgcc_s -lgcc"

CMAKE_CXX_COMPILER_WORKS=1 skips the compiler detection link test (the test program needs libc, but CMAKE_CXX_STANDARD_LIBRARIES isn't applied during detection).

CMake / GCC 13+

GCC 13 added -nostdlib++, which drops libstdc++ while keeping libc, libm, libgcc_s and libgcc. Add libsupc++ for the C++ ABI and libatomic for generic atomic operations that the compiler cannot lower inline:

cmake -S . -B build-with-psychic \
    -DCMAKE_CXX_STANDARD=20 \
    -DCMAKE_CXX_FLAGS="-nostdinc++ -fvisibility=hidden -isystem /path/to/psychicstd/include" \
    -DCMAKE_EXE_LINKER_FLAGS="-nostdlib++" \
    -DCMAKE_CXX_STANDARD_LIBRARIES="/path/to/psychicstd/build-runtime/libpsychicstd.a -lsupc++ -latomic"

No CMAKE_CXX_COMPILER_WORKS needed — libc is still linked by default.

CMake / Clang

cmake -S . -B build-with-psychic \
    -DCMAKE_CXX_STANDARD=20 \
    -DCMAKE_CXX_COMPILER=clang++ \
    -DCMAKE_CXX_FLAGS="-nostdinc++ -fvisibility=hidden -isystem /path/to/psychicstd/include" \
    -DCMAKE_EXE_LINKER_FLAGS="-nostdlib++" \
    -DCMAKE_CXX_STANDARD_LIBRARIES="/path/to/psychicstd/build-runtime/libpsychicstd.a -lsupc++ -latomic"

CMake toolchain file

For CMake projects, the most convenient integration point is the toolchain overlay in cmake/psychicstd-toolchain.cmake. It keeps the compiler choice outside the toolchain and only injects the psychicstd-specific flags. The overlay requires CMake 3.26 or newer.

cmake -S . -B build-with-psychic \
    -DCMAKE_CXX_COMPILER=g++-14 \
    -DCMAKE_TOOLCHAIN_FILE=/path/to/psychicstd/cmake/psychicstd-toolchain.cmake

The toolchain is designed to compose with user flags and sanitizer settings. It adds an internal static-library target to the consuming build and links it to targets declared after the top-level project() call, so no separate psychicstd build step or archive path is needed. If you already have a generated toolchain file, include the psychicstd one after it from a small wrapper toolchain file.

CMake FetchContent

Projects that can link psychicstd explicitly may fetch and build it as a normal static-library dependency:

include(FetchContent)
FetchContent_Declare(
    psychicstd
    GIT_REPOSITORY https://github.com/pauldreik/psychicstd.git
    GIT_TAG main
)
FetchContent_MakeAvailable(psychicstd)

target_link_libraries(your_target PRIVATE psychicstd::psychicstd)

Pin GIT_TAG to a specific psychicstd commit when reproducible dependency resolution is important. Tests and benchmarks default to off when psychicstd is fetched. Linking the target supplies the replacement headers, static runtime, and required ABI-library flags.

Every target containing C++ sources, including fetched dependencies, must use the same standard library. Link psychicstd::psychicstd to each such target or use the toolchain overlay when replacing the standard library for an untouched project or its whole dependency graph.

Using with Conan

If you already use Conan, the intended integration point is a single overlay profile: tests/conan_project/psychic.profile. It composes with your existing host profile and injects the psychicstd toolchain without selecting a compiler or adding dependency-specific flags.

Apply the overlay to the host context so your application and its linked C++ dependencies use the same standard library. You can compose the profiles directly on the command line. Conan applies repeated host profiles in order, so put the psychicstd overlay last:

conan install . \
    --profile:host=your-host.profile \
    --profile:host=/path/to/psychic.profile \
    --profile:build=your-build.profile \
    --build=missing

Alternatively, create a wrapper host profile, for example psychic-host.profile, that includes both profiles in the same order:

include(your-host.profile)
include(/path/to/psychic.profile)

Then pass that single composed host profile to Conan:

conan install . \
    --profile:host=psychic-host.profile \
    --profile:build=your-build.profile \
    --build=missing

The host profile controls the application and the libraries linked into it. The build profile controls tools that run during the build, so it normally remains unchanged.

The example in tests/conan_project/ uses fmt to show a real third-party dependency built this way. The profile does not overwrite sanitizer flags, so ASan and UBSan keep working the way Conan or your project already configures them. Supported compilers are the same as the toolchain-overlay path: Clang and GCC 13+ on Linux.

The overlay includes the psychicstd release version in Conan package IDs, so Conan does not confuse binaries built with psychicstd with those built with the normal standard library. For local experiments, consider setting CONAN_HOME to a separate directory to keep psychicstd-built packages out of your normal Conan cache.

Notes for all configurations

-fvisibility=hidden prevents psychicstd's symbols from interposing with libstdc++ at runtime. -isystem (rather than -I) suppresses warnings from psychicstd headers.

To switch back to the system STL, configure without these flags. See tests/external_project/run.sh for a self-contained working example.

Autoconf/make

Pass flags as ./configure arguments so they are baked into the Makefile — plain make then works without extra flags:

./configure \
    CXXFLAGS="-std=c++20 -nostdinc++ -isystem /path/to/psychicstd/include" \
    LDFLAGS="-nodefaultlibs" \
    LIBS="/path/to/psychicstd/build-runtime/libpsychicstd.a -lsupc++ -latomic -lm -lc -lgcc_s -lgcc"
make

-nodefaultlibs drops the default libstdc++; the explicit LIBS supply the necessary C++ runtime support (exceptions, operator new/delete, etc.). GCC's own library search path is unaffected so -lsupc++ is found without a full path.

Building and testing psychicstd itself

Build and run tests

cmake -B build
cmake --build build
ctest --test-dir build --output-on-failure

The default build covers the library itself — no third-party code, no network access:

  • each implemented header compiled against both the system STL and psychicstd, to check behavioral equivalence
  • a self-containment test per header: including just that one header (and nothing else) must compile
  • a simulated external project that uses the psychicstd toolchain overlay, including ASan and UBSan variants

Testing on real-world projects

Correctness in practice is verified by compiling — and running the test suites of — actual third-party projects against psychicstd. This is what is used to decide what to implement in the library — there is no need to make it slower by implementing things no one uses (yes, I am talking about you, valarray).

Benchmarks

Benchmarks are central to reasoning about performance. Claims have to be backed up!

The benchmarks are listed in order of importance:

name command what it measures
Real-world projects scripts/benchmark_realworld.py Complete third-party project builds with the platform standard library and psychicstd. This is the primary benchmark.
Header compile cost scripts/benchmark_header_cost.py Every public header in isolation, including strict and drop-in modes. This helps locate parsing and transitive-include costs. Results are written to include_weight.md.
Focused compile time scripts/benchmark_compile_time.py Stable example and focused workloads, measuring both elapsed compilation time and peak compiler RSS. Results are written to compile_time.md.
Startup time cmake -B build/ -S . -DCMAKE_BUILD_TYPE=Debug
cmake --build build/ --target startup_bench
Exec-to-exit time for a representative small program. Results are written to startup.md.

Run ./run_benchmarks.sh QUICK on an otherwise idle machine for Debug-only real-world measurements with an estimated one-hour time budget. ./run_benchmarks.sh FULL measures both Debug and Release with a six-hour budget, capped at nine repetitions per project. Both modes regenerate every report and disable ccache automatically.

Compiler memory matters because it controls how many compiler jobs can run in parallel and whether a development build starts swapping. In an uncached GCC 14 measurement, psychicstd used roughly half the peak compiler memory:

workload libstdc++ peak RSS psychicstd peak RSS reduction
wordcounter 114.5 MiB 51.9 MiB 55%
<iostream> test 71.6 MiB 37.1 MiB 48%

For a deeper look at why psychicstd compiles faster, see the per-header case studies, which use clang's -ftime-trace to break down where the time goes:

Standards compliance

Psychicstd uses the libc++ unit tests to partially ensure the library is standards compliant. You need to check out the source code separately from https://github.com/llvm/llvm-project.

Running these tests takes a very long time - they are extensive! For that reason, by default only a random subset is used for each header.

Note that those tests are sometimes libc++ specific - there is not necessarily anything wrong just because a particular test does not pass.

Psychicstd prioritizes getting real projects running (that is: compiling and passing their unit tests) rather than maximizing the score on the libc++ tests. Run tools/compliance.py with:

tools/compliance.py

It samples up to 15 libcxx test files per header, runs them against both the system STL (as a baseline filter) and psychicstd, and updates compliance.md with per-header results. Each header gets two indicators:

  • Compliance — what fraction of the sampled libcxx tests pass: 🟢 ≥80%, 🟡 compiles but fewer pass, 🔴 does not compile with psychicstd.
  • Speed — median compile time of the libcxx test files, psychicstd vs system: 🟢 >1.2×, 🟡 similar, 🔴 slower.

See the results in compliance.md.

The same suite can be run under AddressSanitizer and UndefinedBehaviorSanitizer (tools/compliance.py --sanitize) to catch memory bugs, UB, and behavioral divergence, gated against a baseline of known failures. See docs/sanitizer-testing.md.

About

A C++ standard library optimized for fast compilation

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages