3,239 karma · joined June 17, 2012
Between the two of us, I am looking at a well-researched and widely understood example of why a disparate outcome doesn't actually imply what we would intuitively think, while you are talking about metaphors of smoke and "taking closer looks" as if those justify angrily jumping to conclusions.
If you have some sort of correlation coefficient to justify jumping to conclusions even mildly, I'd appreciate that contribution. But "something isn't equal" isn't even necessarily reason to look into something further, let alone a reason to presume the cause. Nothing is ever equal. Equality is a fictional concept, and doing deep dives into every example of it would exhaust our collective resources.
Have you ever seen any process of filtering job candidates that doesn't have massive failures? Look no further than sports. There is no profession with more accessible information about candidate qualifications, and we still see significant failures, signing people to hundreds of millions of dollars who never produce any value over a replacement player. Does that mean the process is no better than throwing darts at phone book? No. There are still actual skills that are hard to attain, and are decent predictors of performance.
When it comes to CEOs, despite the fact that this process doesn't value YOUR skills very highly doesn't mean it's purely random. It means you've either chosen to ignore this filtering process, you never learned what the process is, or you aren't good enough to stand out within it. But that doesn't mean it's a zero-signal filtration.
I'm not in his head, but it sounds like his ideal job would be professor.
Let's presume that second paragraph is true. He doesn't have that job, let alone have it at a top university. He hasn't been given a PhD (a de facto minimum requirement for that job). UIUC is great, but in a tier below other schools, who presumably didn't admit him (or made him pay too much).
But saying he's "ranked in the top 1%" is very disingenuous to this conversation. The 99th percentile basketball doesn't make a college team. And I'm saying he belongs on the NBA All-Star team. And yes, it would be a "catastrophically bad" bad process if Anthony Davis couldn't have made a college team. But the difference between 99th percentile and 99.9999th percentile is enormous, and mistaking one for the other in any context is terrible.
"However, ever since grade school, I've always sucked in terms of grades and standardized tests. I graduated from Palo Alto High School in the bottom quartile among all the college-bound students. My GPA was barely a 3.0 at graduation, so it was somewhat miraculous that I got accepted into Northwestern University at all."[1]
I know he has a masters and all, but he is spectacular. Hundreds of thousands of people have masters degrees. He is more impressive than 99% of professors, and academia doesn't even acknowledge him as a peer of them.
His intelligence and openness, despite no one paying him for it, shines such a bad light on the terrible state of academia. That he was considered a "bad student" is near-proof in and of itself that our system judges people catastrophically poorly.
Sure. But that limit is one cycle--not two. This is getting pretty above my pay grade.
That tool is nifty, but I couldn't really figure out why it supports your assertion. I plugged in the two loop bodies and got these predicted throughput results:
Unrolled 4x: uiCA 6.00 llvm-mca 6.20
Regular: uiCA 2.00 llvm-mca 2.40
LLVM pretty strongly supports my experience that unrolling/reordering should be a substantial gain here, no? uiCA still has a meaningful gain as well.
You are reaching the limits of my understanding, but my level of knowledge is that store may have reciprocal throughput of 2, but it only occupies two ops (from double pumping a single one) over those two cycles, while the CPU pipeline can handle doing 10. For store in particular, nothing is dependent on it completing, so it can be "thrown into the wind" so to speak. But here's my approximation of the pipeline of a single thread, where dashes separate ops
LOADU.0 - LOADU.1 - _ - _ - _ - _ - ADD.0 - ADD.1 - _ - _ - CMP.0 - CMP.1 - _ - _ - _ - _ - ADD.0 - ADD.1 - _ - _ - STORE.0 - STORE.1 - [start again, because nothing is dependent on STORE completing]
So, that's 10 ops and 12 empty spots that can be filled by simultaneously doing 1.2 more loops simultaneously.
I do want to know why clang isn't using the masked load/store. If it's willing to do it on a dot-product, it should do it here as well. It makes me want to figure out what is blocking it (usually some guarantee that 99.9% of developers don't know they're making).
I do not have the same experience as you with unrolling AVX512 loops on Zen 4. I recall even with double pumping, you can do 2.5 per cycle. As you noted with the stores, it takes two cycles, so you can put 4 into a 3.25 cycle pipeline, instead of 8 cycles. With 5 dependent ops covering 4.5 cycles, this should be a significant win.
I'm not defending the 31 length loop to clean up the mod32 leftover section. That is bad. But it doesn't answer why 256B is 4x slower than line speed and not significantly faster than the unrolled intrinsic version.
In my experience, I never used the masked load store because some platforms did an actual read over the masked-away parts, and could segfault. I recall hearing from a reliable source that Zen 4 doesn't do that, but didn't see official documentation for it. Clang may actually be avoiding the masked cleanup for that reason.
To top it off, I also always found it faster to just stagger the index back instead of a masked load/store whenever it's longer than 64 on calculations like this. That is, if it's size=80, do 0-63, and then 15-79 (which is an optimization Clang doesn't do either for some reason).
Finally, what really really confuses me is that whenever I write a benchmark like this:
for(size_t i = 0; i < sizeof(src); i++) {
src[i] = (uint8_t)(i & 63) + 32;
}
-O3 will do something absurd like just return the answer, since the inputs are constexpr. I can understand why the intrinsic version might confuse the compiler, but the clearly written one should totally have broken the benchmark and overwritten it with a constexpr answer.This is yours. Basically the same instructions, but taking up way more space:
vmovdqu64 zmm3, zmmword ptr [rsi]
vpaddb zmm4, zmm3, zmm0
vpcmpltub k1, zmm4, zmm1
vpaddb zmm3 {k1}, zmm3, zmm2
vmovdqu64 zmmword ptr [rdi], zmm3 vmovdqu64 zmm3, zmmword ptr [rdi + rcx]
vmovdqu64 zmm4, zmmword ptr [rdi + rcx + 64]
vmovdqu64 zmm5, zmmword ptr [rdi + rcx + 128]
vmovdqu64 zmm6, zmmword ptr [rdi + rcx + 192]
vpaddb zmm7, zmm3, zmm0
vpaddb zmm8, zmm4, zmm0
vpaddb zmm9, zmm5, zmm0
vpaddb zmm10, zmm6, zmm0
vpcmpltub k1, zmm7, zmm1
vpcmpltub k2, zmm8, zmm1
vpcmpltub k3, zmm9, zmm1
vpcmpltub k4, zmm10, zmm1
vpaddb zmm3 {k1}, zmm3, zmm2
vpaddb zmm4 {k2}, zmm4, zmm2
vpaddb zmm5 {k3}, zmm5, zmm2
vpaddb zmm6 {k4}, zmm6, zmm2
vmovdqu64 zmmword ptr [rdi + rcx], zmm3
vmovdqu64 zmmword ptr [rdi + rcx + 64], zmm4
vmovdqu64 zmmword ptr [rdi + rcx + 128], zmm5
vmovdqu64 zmmword ptr [rdi + rcx + 192], zmm6
...which is, upon first glance, is similar to yet better than the intrinsics version you wrote. Additionally it has cleaner tail handling.