But the reality is that almost no one actually cares about performance because compute is cheaper than expertise and labor, at least in the short haul. Everything is getting more bloated and slower and we just compensate by adding CPU cores, gigabytes and gigahertz.
It's wild that such a fundamental piece of code (you can't really implement operation on structs without those) is library-supplied. I wish compilers would just have something like __builtin_memcpy and __builtin_memset, and provided some highly optimized, specialist-crafted assembly in those, instead of having to inline the library code and hopefully be able to optimize it.
Glibc, for example, has perhaps ten different implementations of memcpy just for x86. The compiler certainly could do provide all that, but the next step is harder:
glibc automatically dispatches to the proper one at runtime based on the actual microarchitecture that the binary is running on. You pay the extra dispatch cost once, but all of non-inline function call cost every time. This is what allows distros to compile to a nice baseline architecture, but still get near-optimal memcpy performance on many more architectures than a single inline instance could possibly give. These differences matter.
And it does it for not just memcpy, but half-a-dozen other extremely performance sensitive library functions, like strcpy and so on.
Inlining works very much against this strategy. If you can guarantee that the target microarch never changes, then it isn't a good one. But that is somewhat unusual for everyone but those who build their own binaries to run on a single class of machines forever.
Worse, inlining the really high performance versions of these ends up being terrible from a code size perspective, because they are often hundreds of instructions, which can have bad caching effects. And once you amortize the function-call cost over many iterations of the loop, it isn't so expensive to call out to the library.
Anyway, just some additional considerations to think about.
The ones provided by the compilers are simply the libc ones.
LLVM will even go as far as detect attempts to rewrite memcpy and replace them with a call to the libc one!
The problems at first glance :
- Not having control over the implementation detail of the interface that your library provides is probably not wise. Sounds like a lot of bad bug reports and edge cases that you have no control over.
- Not all compilers may provide these.
Moreover the 26% is with mimalloc, with musl's allocator it's 144%, so there are likely other parts that are slower (likely the memcpy implementation)
For a much more technical discussion, see https://github.com/sharkdp/fd/issues/710
That to me is the main driver for MUSL.
If this specific use case is of high interest to you and you have some available bandwidth, contributing to it, maybe becoming a maintainer, and eventually organising a tier 2 MCP would definitely be a good idea.
You could add alloc with a custom global allocator, but I don't even know what high perf global allocator you could use that wouldn't need libc. Jemalloc and mimalloc are out. Some embedded allocators would work (but those are rarely high performance, instead being optimised for small code and data footprints).
That said, with enough effort (quite a lot!) it would be possible to add support for alloc and std without libc on Linux specifically (since it has a stable syscall ABI).
What might be more realistic though is looking at relibc (a rust implementation of libc, made for Redox OS but from what I read it also supports Linux). But I haven't tried it and I don't know the state (or goal) of it.
This is addressed and disputed very early in the article. The very first benchmark presented shows a 26% regression using musl + mimalloc, a high-performance 3rd party allocator.
And the compounding issue is that the allocator issues get significantly worse as parallelism increases, as the allocator is serial, so as concurrency increases so does the impact of the allocator, which is not the case for most of the "regular slow" code (of musl), those have a relatively constant overhead per thread.