> the slowdown is coming from (probably) slow musl implementations of memcpy/memset.
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.
> I wish compilers would just have something like __builtin_memcpy and __builtin_memset
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!
These builtins of course exist, it's how compilers keep track of the behavior of these functions.
For GCC, there is -minline-all-stringops:
https://gcc.gnu.org/onlinedocs/gcc-16.2.0/gcc/x86-Options.ht...
It does what it says, but the results may not be what you expect.
Maybe you're being sarcastic, but I'm pretty sure clang + gcc do offer these.
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.
Clang and GCC do provide these, and automatically use them in many situations (particularly small copies). But c-libraries can actually do it better in many cases, especially for large copies.
Glibc, for example, has perhaps ten different implementations of memcpy just for x86. The compiler certainly could 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.