Skip to content

RFC: Replacing tinyformat with {fmt} #33942

@hulxv

Description

@hulxv

Description

Bitcoin Core has used tinyformat (via its strprintf wrapper) for text formatting for many years, a choice that reflected the library’s simplicity and its ability to be dropped into a large C++ codebase with minimal friction. Over time, the C++ standard and the ecosystem around formatting have evolved: {fmt} has become the de facto modern formatting library and served as the basis for std::format in later C++ standards. At the same time, a security and maintenance review of Tinyformat in the Bitcoin context revealed limitations that required ad-hoc fixes. Replacing tinyformat with {fmt} is therefore not merely an API swap; it is an alignment with modern C++ conventions, with a maintained codebase and stronger compile-time guarantees that reduce future maintenance cost.

The primary motivations for replacing tinyformat with {fmt} can be expressed in concrete, measurable terms. Benchmarks maintained by the {fmt} project and reproduced by early adopters show that {fmt} formats common workloads at speeds close to printf and substantially faster than Tinyformat (which relies on iostreams). In one commonly cited microbenchmark suite, {fmt} completes a heavy formatting loop in roughly 1.3–1.4 seconds while tinyformat finishes the same workload in roughly 2.25–2.6 seconds, which implies an empirical throughput improvement on the order of 1.6x to 2x for formatting-heavy tight loops. These measurements come from the {fmt} benchmark collection and independent reproductions of the original tinyformat benchmark.

Beyond per-call speed, the difference in generated code and compile-time behavior is also material. The {fmt} project’s benchmark table reports much smaller stripped binaries and faster compile times for equivalent test programs: for a medium test scenario, {fmt} produced a stripped binary in the tens of kilobytes while the tinyformat/io-based alternative produced a binary in the low hundreds of kilobytes, and {fmt}’s measured compile times were lower than the iostream/tinyformat combination in the sample runs. These results indicate that adopting {fmt} can reduce the code-size impact of formatting and improve build characteristics on large codebases where formatting is used frequently.

Metric {fmt} tinyformat printf / iostream (for reference)
Execution time for a heavy formatting loop (2M iterations) 1.42 s 2.25 s printf ~1.30 s, iostream ~1.85 s.
Measured throughput improvement (tinyformat -> {fmt}) {fmt} ~= 1.6x faster than tinyformat
Stripped binary size for a medium test project 34 KiB 386 KiB
Compile time for that medium test project 46.8 s 64.6 s
Approximate binary-size reduction (tinyformat -> {fmt}) {fmt} produces binaries ~11× smaller in this scenario (386 KiB -> 34 KiB)

reference for benchmarks results from {fmt} team:
https://github.com/fmtlib/fmt?tab=readme-ov-file#benchmarks

In addition to all of that, I think that will be a good step toward migrating to C++20 in the future, and we will be able to depend on the standard format library itself.

There were a lot of discussions to replace tinyformat with {fmt}, and I think the time has come to do that.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions