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

by karageniton 9/1/24, 8:30 AMwith 128 comments
by magnioon 9/1/24, 9:10 AM

> 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_pl4n3ton 9/1/24, 9:13 AM

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 pzmarzlyon 9/1/24, 9:44 AM

> 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_exploreon 9/1/24, 9:43 AM

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 ptsptson 9/1/24, 11:48 AM

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 a1oon 9/1/24, 11:27 AM

> 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 neonsunseton 9/1/24, 10:12 AM

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 msephtonon 9/1/24, 11:26 AM

Very enjoyable. I love these sort of thinking outside the box optimisations.

by rty32on 9/1/24, 12:27 PM

Maybe I am slow, it took me a while to realize the "14k" in the title refers to "14kB"