Essentially this type of thing is the only good way to improve realism. And it allows rendering to move eventually from what is basically an aggregation of different types of lighting approximations towards a more unified global illumination system. It is going to require engine rewrites and a few more generations of hardware improvements to fully achieve that, but it will enable real-time rendering close to today's cinema quality and also streamline game development by removing the need for so much asset preparation related to lighting.
Anyway I think that AMD absolutely needs to catch up in this area. Even though many people related to gaming or programming may have trouble recognizing the relevance because all of the lighting hacks are firmly baked into the culture and accepted. All those hacks will gradually become obsolete.
In fact, AMD has some tricks up its sleeve. AMD Zen has two AES pipelines (while Intel Skylake only has one), so vectorized AES code is faster on AMD.
There are some performance issues with vpgatherdd instructions, but Intel has those as uop emulated code too. So both Intel and AMD are equally to blame there.
----------
My "issues" with AMD CPUs are relatively tame. AMD's profiling tools are weaker than Intel's. (Ex: AMD has "Instruction Based Sampling" while Intel's "PEBS" (Precision Event Based Sampling) are a bit easier to use. True 256-bit execution would be nice, but its not a major hassle in most cases IMO: AMD's back-end is very thick, so you can still get a lot of ILP out of AVX2 instructions.
Isn't HPET is just a basic system timer? (That is definitely present on Zen.) You might be thinking of something else?
> 256-bit execution would be nice, but its not a major hassle in most cases
Coming with Zen 2 anyway :)
You're right. I got the names confused. I've edited the post above to use the proper "PEBS" term.
Intel's "Precise Event-Based Sampling" is what I was trying to talk about. Intel's PEBS can precisely tell you where a branch-mispredict happens.
AMD's default event timers are inaccurate: your branch mispredictions will be all over "add" instructions, and other unrelated stuff. This is because a CPU is looking at roughly ~100 instruction windows (between the pipeline, decoder, and retirement... there's a lot of inaccuracy in determining "where did this branch misprediction happen??").
So when trying to track down a branch-misprediction on AMD systems, you have to switch to the harder to use "Instruction based Profiling" mode. Intel has a simpler PEBS switch which is easier to use IMO.
This slow scaling of 128->256->512 bits in the instruction set is more or less a solved problem in the GPU space with shader compilers and AVX would mostly be redundant with GPUs if it weren't for the memory bandwidth constraint.
ie: When it comes to vector processing, go big or go home.
AVX/SSE are a compromise from back when CPU die space and bandwidth was more precious. Now that we have 8-32 cores on a die with a good bus between them, it seems like duplicating those AVX units 8-32 times is less optimal.
AVX's main advantage is that it is roughly 1-cycle away from your main registers, and 4-cycles away from L1 cache. Talking to and from L3 cache is on the order of 30 to 40 cycles, an order of magnitude slower.
If your workload fits inside of 64kB, AVX is incredibly beneficial. If your workload fits within 8MB (L3 cache), you're starting to look at a point where maybe you should pipe that data to the GPU instead.
A GPU call over PCIe is under 5 uS / 5000 nanoseconds, with a bandwidth of ~15GB/s. GPUs are certainly farther away than L3 cache, but if you're pushing L3 cache levels... you're getting close to the GPU anyway.
---------
128-bit SSE code is perfect for representing Complex Numbers (Two double-floats). 128-bit is also great for a x,y,z,w 32-bit vector.
-------
GPUs are the "go big or go home" architecture. AVX's primary benefit is latency.
Here's the simplest godbolt example I could think of to illustrate, summing a string of fixed length and non-fixed length: https://godbolt.org/z/O_M5fU
You can see that the most recent GCC fails to use the AVX512 zmm registers even after being configured to do so (afaik) and also fails to use more than 4 registers. Clang does better, using zmm* and all the registers.
But in both cases, the amount of code generated is quite large. If you compile with -Os instead of -O3, no vector instructions are used for some reason.
So when you load this code, no matter what, you're loading a bunch of instruction cachelines, which will destroy most of your latency gain unless the input is very large. And even if your input is large, you'll fault that data anyways.
So what's the point of doing this on the CPU again?
Changing the code to 32-bit ints results in the "vpaddd zmm0, zmm0, ZMMWORD PTR [rdx]" that you'd expect from the auto-vectorizer.
> You can see that the most recent GCC fails to use the AVX512 zmm registers even after being configured to do so (afaik) and also fails to use more than 4 registers
In the 32-bit vpaddd code... "vpaddd zmm0, zmm0, ZMMWORD PTR [rdx]" becomes a new zmm register allocation in the register-renamer (due to a cut dependency). I doubt any code would be any faster.
In any case, its not about "the number of registers used", proper analysis is about the depth of dependencies. I'd only be worried about small-register usage if the dependency chain is large (which doesn't seem to be the case).
EDIT: I had some bad analysis. I've erased the bad paragraph.
So it seems pretty good in my eyes.
So already it's better than "pretty good" and it's still verbose.
But neither of these compilers are able to optimize for code size while using AVX, so the ((code size)/64byte cacheline) * (~100ns loads) will still kill your performance on any data set that's smaller than a few kilobytes.
You specified the "int sum", so that means "sum" needs to follow 32-bit overflow semantics. I don't think it is possible to do 8-bit
> But neither of these compilers are able to optimize for code size while using AVX
You can totally do that. You're specifying -O3, which is "speed over size". If you want size, you want -Os, and then use -ftree-vectorize to enable the auto-vectorization.
The results are often counterintuitive, because it's hard for a human to account for how the program _actually_ gets executed, which instructions go in which order, port utilization, data dependencies, CPU bugs, effects of alignment (or, often, lack thereof on Intel CPUs), micro-op caching, etc etc. For all you know GCC deliberately avoided the use of AVX512 registers because AVX512 causes the CPU to throttle the clock.
I've also found that ye olde -O2 flag recommendation which says -O3 is likely to be slower in practice because it produces bulkier machine code, nearly never holds up anymore.
That said, I concede that autovectorization is not perfect. I can almost always beat it if I really want to, sometimes by quite a margin. But what you're proposing is unlikely to help matters, especially for smaller inputs that you're concerned about.
You are welcomed to load up the godbolt example and try and create better assembly using O2 or whatever else you can think of. You can build the code on your machine and benchmark it too.
But at the end of the day, the compiler output sucks. It's clearly optimized for synthetic benchmarks that chug through large datasets using a tiny amount of code that can fit in L1 cache even after the compiler blows up its footprint.
But my point is that these large datasets with tiny processing kernels (like a video codec for example) are much better suited to different kinds of processors.