Optimising Memset and Memcpy
twitter.com
twitter.com
There are a ton of tradeoffs that have to be made in the microarchitecture and in general the focus is real code and normal expected sizes rather than benchmarks. Big page multiples are common and then variable sized calls that can be totally unaligned and random sizes.
A proper study of this would test a pile of different mixes of sizes and alignments and test on a bunch of different processor generations.
And then you get results where a hand coded memcpy loop is faster in benchmarks, but in practice in a big program you might find that 'rep movs' is better because of the smaller code footprint. When you only optimize the few cases that matter.
The general rule is that the compiler and system libraries do a really good job optimizing for processors that shipped 10 years ago. So architects get to make decisions about how to help these operations. You can add a nifty feature that makes it faster (like CLZERO) but while it is good for benchmarks it will takes years after you ship before normal programs benefit. You might optimized 'rep movs' but then you find that since it was slow in the past most real system librarys don't call it.
We are very much entering the age where mechanical sympathy is waning
I guess if you run it often enough it could still give useful numbers, but I understand the author is picking the best run.
Also I don't see to be any attempt at avoiding compiler optimisations.
Benchmarking is hard
I used to do assembly and count cycles, but now I wouldn't dare; it's hardcore compiler and library makers stuff. It's like "don't do your own crypto (optimization)".
Everyone knows why it is so, though - we cannot solve the problem by throwing more Gigahertz at it.
Writing your own crypto is very different; the stakes are higher if you get it wrong.
Is there at least a checklist? It should ideally be automated. IIRC there's some library, maybe BLAS, that at compile time computes some machine-specific constants and also refuses to do it if a few things are not right.
Rant continued. I would prefer if the performance sections of papers were restricted to single paragraphs and that artifacts were required. When I'm reading papers I'm more interested in their ideas than in the third decimal place of the result on that given day on some machine I don't have. The Unix Timesharing System has no performance results. The Case For A Reduced Instruction Set Computer has no performance results. The notion that not only having results is somehow important but that providing the testing methodology and hardware configuration is also necessary is nonsense. It just lards up papers and a couple of years later the cited configuration is irrelevant.
The CRAY-1 Computer System has no performance results but its section on vector processing still rings true today. It's what the RISC-V vector extension uses.
https://github.com/llvm/llvm-project/blob/main/libc/src/stri...
1. vxorps %xmm0, %xmm0, %xmm0
2. vmovups %ymm0, 11(%rdi)
3. vmovups %ymm0, (%rdi)
4. vzeroupper
So line 1 sets xmm0, a 128 bit/16 byte register, to 0 (by xoring it with itself). I suppose the convention is for the caller to save the register if it wants it. The thing that confused me is that the way 256 bit/32 byte registers work is that ymmX represents the 256 bit register of which xmmX is the lower half. Similar to the way %eax is the lower 32 bits of %rax. I suppose the upper half of %ymm0 is 0 by ABI.So line 2 then writes out this 32-byte register (of zeros) starting at buffer[11] and ending at buffer[42]. The actual instruction name stands for “vector move unaligned packed single precision floats” and I don’t really understand why the precision or float type matters.
Line 3 writes it from buffer[0] to buffer[31]. I guess the overlap isn’t expensive.
Line 4 zeros the upper half of the register which is a no-op in this case but might be useful as it breaks the dependency chain so the cpu can know nothing else will need to use the current upper-half values (but isn’t it useful to break the chain on the lower half too?).
Seems like a lot of the benefit also comes from "Intel processors since < X > no longer care about aligned vs unaligned loads and stores" (so older code that spends effort lining this up doesn't benefit in the small cases up to 512 bytes).
Does that rationale still hold for larger blocks and/or hitting the cache lines awkwardly? Like make something where you'll have plenty of 64-byte copies which are offset enough that they are off alignment and pollute the neighboring cache line (probably most visible for memset).
It's still not always faster: https://stackoverflow.com/questions/43343231/enhanced-rep-mo...
While there are still startup costs, the overhead of calling a function (especially via a PLT) and incurring instruction cache misses is hard to demonstrate in a microbenchmark, while rep movsb encodes more compactly than many flavors of call. In an actual application though, the "slower" but smaller implementation can often win (https://research.google/pubs/pub50338.pdf and https://research.google/pubs/pub48320.pdf)