9 ms·
Honey, I shrunk {fmt}: bringing binary size to 14k and ditching the C++ runtime
- a1o 2y ago> 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!
- JonChesterfield 2y agoIt varies widely with whether the C library is dynamically or statically linked and with how the application (and C library) were built. And on which C library it is. Also a little on whether you're using elf or some other container.
- magnio 2y ago> All the formatting in {fmt} is locale-independent by default (which breaks with the C++’s tradition of having wrong defaults) Chuckles
- tialaramex 2y agoIt's really more of a committee thing - so we wouldn't necessarily expect fmt, a third party library, to have wrong defaults. Astoundingly, when this was standardised (as std::format for C++ 20) the committee didn't add back this mistake (which is present in numerous other parts of the standard). Which does give small hope for the proposers who plead with the committee to not make things unnecessarily worse in order to make C++ "consistent".
- formerly_proven 2y agoI'm filing a Defect Report about std::format disrespecting locale as we speak.
- arunc 2y agoHow/where do you do that?
- vitaut 2y agoYou send an email to the Library Working Group chair.
- johannes1234321 2y agoSee https://isocpp.org/std/submit-issue https://isocpp.org/std/submit-issue
- tialaramex 2y agoOf course, just because a defect is reported doesn't mean it'll get fixed, or that the fix will be of any use. The most famous (technically a C defect) is probably DR#260: https://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm
- ape4 2y agoYou can pass in a locale as a parameter. (Of course this doesn't fix the default)
- h4ck_th3_pl4n3t 2y agoIt'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 https://github.com/jk-jeon/dragonbox
- mananaysiempre 2y ago> It's kind of mindblowing to see how much code floating point formatting needs. If you want it to be fast. The baseline implementation isn’t terrible[1,2] even if it is still ultimately an implementation of arbitrary-precision arithmetic. [1] https://research.swtch.com/ftoa https://research.swtch.com/ftoa [2] https://go.dev/src/strconv/ftoa.go https://go.dev/src/strconv/ftoa.go
- vitaut 2y agoIf I interpret the numbers correctly it is of the order of ~1000 times slower than modern algorithms such as Dragonbox.
- mananaysiempre 2y agoSomething like that. The Dragonbox author reports[1] about 25 ns/conversion, Cox reports 1e5 conversions/s, so that’s a factor of 400. We can probably knock off half an order of magnitude for CPU differences if we’re generous (midrange performance-oriented Kaby Lake laptop CPU from 2017 vs Cox’s unspecified laptop CPU ca. 2010), but that’s still a factor of 100. Still a performance chasm. You can likely get some of the performance back by picking the low-hanging fruit, e.g. switching from dumb one-byte bigint limbs in [0,10) to somewhat less dumb 32-bit limbs in [0,1e9). But generally, yes, this looks like a teaching- and microcontroller-class algorithm more than anything I’d want to use on a modern machine. [1] https://github.com/jk-jeon/dragonbox/blob/master/README.md#performance https://github.com/jk-jeon/dragonbox/blob/master/README.md#p...
- vitaut 2y ago{fmt} has an optional implementation of the old Dragon4 algorithm that is smaller in terms of code size but not as fast.
- londons_explore 2y agoI 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...
- vient 2y agoIt is a featureful formatting library, not simply a library for slow printing of ints and strings without any modifiers. You can't create a library which is full of features, fast, and small simultaneously.
- jstimpfle 2y agoYou'd hope the unused stuff gets stripped out but I don't know much about this topic so not going to argue.
- vlovich123 2y agoFfunction-sections and fdata-sections would need at a minimum to be used to strip dead code. But even with LTO it’s highly unlikely this could be trimmed unless all format strings are parsed at compile time because the compiler wouldn’t know that the code wouldn’t be asked to format a floating point number at some point. There could be other subtle things that hide it from the compiler as dead code. The surest bet would be a compile time feature flag to disable floating point formatting support which it does have. Still, that’s 8kib of string formatting library code without floating point and a bunch of other optimizations which is really heavy in a microcontroller context
- 2y ago
- pzmarzly 2y ago> 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?
- murderfs 2y agoNo, modulo the aligned allocation overloads, but applications are allowed to override the default standard library operator new with their own, even on platforms that don't have an equivalent to ELF symbol interposition.
- masklinn 2y agoThat doesn't really explain where the dependency on the C++ runtime come from tho, as far as I know the dependency chain is std::allocator -> operator new -> malloc, but from the post the replacement only strips out the `operator new`. Notably I thought the issue would be the throwing of `std::bad_alloc`, but the new version still implements std::allocator, and throws bad_alloc. And so I assume the issue is that the global `operator new` is concrete (it just takes the size of the allocation), thus you need to link to the C++ runtime just to get that function? In which case you might be able to get the same gains by redefining the global `operator new` and `operator delete`, without touching the allocator. Alternatively, you might be able to statically link the C++ runtime and have DCE take care of the rest.
- kllrnohj 2y agoYes they could have just defined their own global operator new/delete to have a micro-runtime. Same as you'd do if you were doing a kernel in C++. Super easy, barely an inconvenience
- vitaut 2y agoChanging global new/delete is a non-starter in a reusable library. Allocator is a much more localized change and roughly the same amount of work.
- neonsunset 2y agoIt'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.
- pjmlp 2y agoStill looking forward for the Delphi like experience with Native AOT, thankfully getting better.
- msephton 2y agoVery enjoyable. I love these sort of thinking outside the box optimisations.
- ptspts 2y agoShameless plug: printf(Hello, World!\n"); is possible with an executable size of 1008 bytes, including libc with output buffering: https://github.com/pts/minilibc686 https://github.com/pts/minilibc686 Please note that a direct comparison would be apples-to-oranges though.
- jart 2y agoThat's because the compiler turns it into fputs
- rty32 2y agoMaybe I am slow, it took me a while to realize the "14k" in the title refers to "14kB"
- hrydgard 2y agoWhat else would it possibly mean? k is very common shorthand for kB, at least historically.
- Rygian 2y ago14000 lines of assembler?
- rty32 2y agoSomething along that line (oops, pun not intended). I was thinking 14000 of something, not sure exactly what that is, until I realized it was kind of obvious. Indeed a decade ago I would not have any doubts. Using "k" to refer to "kB" was much more common.
- cokeandpepsi 2y ago[dead]