Fast memcpy, a system design
sigarch.org
sigarch.org
Here's a different thought experiment: how much of that data movement is actually necessary? If data movement was taking up so much time, why not investigate why and if that's even necessary, instead of thinking it's the hardware that needs to be faster? As the old saying goes, "the fastest way to do something is to not do it at all." One style of code that seems to be particularly exacerbated by the widespread use of HLLs is making lots of unnecessary temporary copies of data. The biggest inefficiency isn't the hardware (unless your goal is to sell more hardware, but that's a different rant...) --- it's the insanely complex and bloated layers of software that gets layered on top of what is otherwise perfectly adequate hardware. From that perspective, faster hardware only leads to even more wasteful and inefficient software. Making memory copies faster will only cause software to make more unnecessary copies.
But after reading the whole thing, I can say that this is certainly a very strange article. It's written by someone who is presumably quite knowledgeable about hardware, yet seems completely oblivious to the fact that x86 has had a dedicated instruction for this since the 8086, and 16 bytes per cycle was already achievable with the P6 (1996) "fast strings" enhancement:
https://stackoverflow.com/questions/33902068/what-setup-does...
I have some coworkers trying to re-architect a big chunk of our application to selectively provide data because "only about 10% is used on each interaction". Nobody has bothered to check what % of the data is never used at all. If it's 50%, then you're complicating a whole lot of code and reuse semantics in order to get a 2-4x improvement in 'performance' that you're selling as a 10x improvement.
But to your bigger question, defensive copies are about keeping someone else from munging your data. Borrow Semantics prevent some of those scenarios, which at least moves some of that inefficiency to compile time, where the tax is paid by the perpetrator and their peers, instead of by the users.
Assuming Google uses gRPC and protobufs extensively, I can only imagine a chaotic polyglot environment where buffers are copied and transformed very often.
I work at a fairly large Python shop using libraries in C. Cost of FFI is non-trivial, memory management and moving data is the usual culprit.
There is so much of this. Is there any hope for a world where we can ship working memory without costly XDR? Does anyone have data on the rate of change of time or energy spent on XDR translation?
I have been working on a new feature in Virgil to make writing explicit byte-by-byte layouts easier and directly supported by the compiler. It's still a work in progress though.
Yes, you can have zero serialization latency: https://capnproto.org/
P.S. I work on the type of systems where we obsess over unneeded memory accesses and copies. Minimizing those is not as trivial as it may seem, especially if you care about memory safety.
Aren't you worried that this will just lead to another manifestation of the Jevons Paradox? I get that you already mention that everyone in your "field" understands that memory is the bottleneck to avoid, but still.
The ultra-bloated unrolled vector implementations may appear a little faster in microbenchmarks but the amount of cache they take up and other effects like AVX downclocking (for those implementations that use AVX) mean that in practice they can actually be slower overall.
E.g., Agner: https://www.agner.org/optimize/blog/read.php?i=372
E.g., Stackoverflow: https://stackoverflow.com/questions/43343231/enhanced-rep-mo...
¹ but you still want to branch around it when the length is zero ("when the length is zero?!" I hear you cry; it turns out that if you instrument actual systems, you will find that this happens shockingly often).
Mateusz guzik says it's decent above 128 bytes, but that software sequences still win below that.
The main trap is trying to benchmark and profile it without the context of the whole application.
I recommend to read a comment in ClickHouse source code (disclaimer: written by me)
https://github.com/ClickHouse/ClickHouse/blob/master/base/gl...
Emery Berger's "Performance (Really) Matters" talk also goes into this[0]. He paints a really, really depressing picture of how unreliable benchmarks truly are. Like, things like link order having ridiculous impacts and stuff like that.
I know there were also measurements about protobuf encode/decode, and I think it was a similarly large portion of the total workload. (Anecdotally I think that's why people say the protobuf libraries are huge and big and slow to compile -- because they're highly optimized that's probably what empirically resulted in the fastest execution time)
It feels like a content-oriented and value-oriented model would have less of this profile, i.e. with (correct) local caching and differential compression
A Hardware Accelerator for Protocol Buffers https://dl.acm.org/doi/abs/10.1145/3466752.3480051
And then when they open sourced it, I guess they left out that code. So the open source version is probably "de-optimized" a bit unfortunately.
These are more or less built in to avx512 already...
(The hot path is: load immediate; bzhi; kmov; load; store. 5 instructions rather than 2, but still trivial. If you look at the _latencies_ involved, the difference is trivial.)\
(Afaik it's in sve too.)
You've got to admire it, really.
What I would really like is to see more widespread hoisting of dispatch. Rather than calling 'memcpy', call 'memcpy_small', 'memcpy8', etc.
The GNU way of using a huge, branchy assembly block that is selected at runtime using ifunc means that the compiler never even got a chance.
Regarding the question of whether or not they are faster, see section 4.4 of this paper. Replacing the glibc memcmp with something trivial resulted in up to 1% speedup in Google web search, when considering the whole program. It doesn't microbenchmark as well as glibc memcmp, but it is a better function that doesn't wreck the performance of the rest of the system.
https://storage.googleapis.com/pub-tools-public-publication-...
I have low hopes for compilers. Inlining heuristics are terribly complicated, and optimisations that result therefrom will only be things that the compiler can prove for _all_ invocations. Inlining won't get you 'this call site is usually large', or 'this call site is small, but predictable', or 'this call site is really unpredictable, so use weird branchfree tricks'. (A JIT compiler might do better, but people do not usually JIT c or c++.)
> Replacing the glibc memcmp with something trivial resulted in up to 1% speedup in Google web search, when considering the whole program. It doesn't microbenchmark as well as glibc memcmp, but it is a better function that doesn't wreck the performance of the rest of the system
It's not all or nothing, and rep cmps is utter trash. My memcmp, for instance, I was careful to keep below 4 cache lines (or 3, considering only the version that doesn't overread)—<https://github.com/moon-chilled/fancy-memcmp/blob/master/mem...>—and it actually has reasonable throughput.
(Also: inlining is not the way to go if you are frontend-bound...)
When workload is frontend-bound, then the culprit is usually either in instruction cache misses (e.g. a lot of unpredictable virtual calls or generally poor code layout) or branch mispredictions (e.g. a lot of unpredictable branches in your code). I fail to see how inlining the code can be correlated to these two effects other than, what I believe is a myth, that inlining the code will by (1) growing the executable size (2) put more pressure on the instruction cache size and (3) therefore end up with degraded performance. In my experience, rarely I have seen even nr. (1) taking place (compilers and linkers are way too smart nowadays) and I think I have never managed to measure the effects of (2) and (3) in a non micro-benchmark scenario.
Anyway, if I were to optimize the workload found to be frontend-bound, eliminating branches and getting rid of the dynamic dispatch would be the first two things to check. Inlining maybe but only maybe in the third place.
Not really a novel idea, particularly since ARM already announced memcpy instructions earlier this year.
There was a silly typo in the very first sentence that threw me off:
When I worked at Google, fleet-wide profiling revealed that 25-35% of all CPU time was spent just moving bytes around: memcpy, strcmp, [...]
I think that should be `strcpy()`, since obviously `strcmp()` doesn't move data around.
Are there modern analogues to that dbcc instruction?
Overhead increases by effectively removing pointers as we know them, replacing them with hash+length, but the reward is lower memory pressure and better cache locality. It also carries the promise of making a "buffer overrun" not only impossible but unthinkable. In such a world, incrementing "one extra" would not step into some other heap or stack, because everything would be segmented.
Surely they are aware of the memcpy problem, so if it's true that servers spend a lot of time copying memory, what is Intel (or AMD) doing regarding this?
In this case they haven't invented new instructions at all but have been steadily improving REP MOVS over time.
You don't always need the operation finished right away. Maybe REP MOVS could return a sort of a handle that you then wait for when you actually need to use the destination, and the CPU can keep chugging along in the background. Like when you submit commands to the GPU or how you can wait at memory fences for other threads in a warp to complete.
https://01.org/blogs/2019/introducing-intel-data-streaming-a...