September 1st, 2024

Honey, I shrunk {fmt}: bringing binary size to 14k and ditching the C++ runtime

The {fmt} library has reduced its binary size to 23kB by using type erasure, ensuring runtime type safety, and optimizing for memory-constrained devices, including retro computing systems.

Read original articleLink Icon
CuriosityAmusementSkepticism
Honey, I shrunk {fmt}: bringing binary size to 14k and ditching the C++ runtime

The {fmt} formatting library has achieved a significant reduction in binary size, now reaching as low as 23kB, while eliminating the need for the C++ runtime. This reduction is primarily due to the library's innovative use of type erasure, which minimizes template bloat and confines template usage to a minimal top-level layer. The library's design allows for efficient formatting without sacrificing runtime type safety, catching format string errors at compile time and managing runtime errors through exceptions. Recent optimizations include disabling locale support and utilizing an extension API for formatting arbitrary types, which further reduces the binary size. The library's performance remains robust, particularly in scenarios involving positional arguments. The latest version of {fmt} has also integrated the Dragonbox algorithm for floating-point formatting, enhancing its capabilities. The ongoing interest in using {fmt} for memory-constrained devices, including retro computing systems, underscores its versatility and efficiency. Overall, the library's development reflects a commitment to optimizing both size and performance, making it a suitable choice for various applications.

- The {fmt} library's binary size has been reduced to as low as 23kB.

- Type erasure is used to minimize template bloat and improve efficiency.

- The library maintains runtime type safety while allowing for compile-time error checking.

- Recent optimizations include disabling locale support and using an extension API for arbitrary types.

- The library is increasingly popular for use in memory-constrained environments, including retro computing.

AI: What people are saying
The comments reflect a mix of technical insights and opinions regarding the {fmt} library's binary size and formatting capabilities.
  • Several users express surprise at the complexity and size of floating-point formatting code.
  • There are discussions about the efficiency of different memory allocation strategies in C++.
  • Some commenters share alternative solutions for minimal binary sizes, including references to other libraries.
  • Users highlight the importance of optimizing for memory-constrained environments, such as microcontrollers.
  • There is a humorous acknowledgment of the challenges faced in numeric formatting across different programming languages.
Link Icon 10 comments
By @magnio - 8 months
> All the formatting in {fmt} is locale-independent by default (which breaks with the C++’s tradition of having wrong defaults)

Chuckles

By @h4ck_th3_pl4n3t - 8 months
It's kind of mindblowing to see how much code floating point formatting needs.

The linked dragonbox [1] project is also worth a read. Pretty optimized for the least used branches.

[1] https://github.com/jk-jeon/dragonbox

By @pzmarzly - 8 months
> However, since it may be used elsewhere, a better solution is to replace the default allocator with one that uses malloc and free instead of new and delete.

C++ noob here, but is libc++'s default allocator (I mean, the default implementation of new and delete) actually doing something different than calling libc's malloc and free under the hood? If so, why?

By @londons_explore - 8 months
I kinda hoped a formatting library designed to be small and able to print strings, and ints ought to be ~50 bytes...

strings are ~4 instructions (test for null terminator, output character, branch back two).

Ints are ~20 instructions. Check if negative and if so output '-' and invert. Put 1000000000 into R1. divide input by R1, saving remainder. add ASCII '0' to result. Output character. Divide R1 by 10. put remainder into input. Loop unless R1=0.

Floats aren't used by many programs so shouldn't be compiled unless needed. Same with hex and pointers and leading zeros etc.

I can assure you that when writing code for microcontrollers with 2 kilobytes of code space, we don't include a 14 kilobyte string formatting library...

By @ptspts - 8 months
Shameless plug: printf(Hello, World!\n"); is possible with an executable size of 1008 bytes, including libc with output buffering: https://github.com/pts/minilibc686

Please note that a direct comparison would be apples-to-oranges though.

By @a1o - 8 months
> Considering that a C program with an empty main function is 6kB on this system, {fmt} now adds less than 10kB to the binary.

Interesting, I've never done this test!

By @neonsunset - 8 months
It's always fmt. Incredibly funny that this exact problem now happens in .NET. If you touch enough numeric (esp. fp and decimal) formatting/parsing bits, linker ends up rooting a lot of floating point and BigInt related code, bloating binary size.
By @msephton - 8 months
Very enjoyable. I love these sort of thinking outside the box optimisations.
By @rty32 - 8 months
Maybe I am slow, it took me a while to realize the "14k" in the title refers to "14kB"