That's an interesting thought, though in this particular case I think it's a little misguided. 8-bit arithmetic (or, god forbid, 16-bit arithmetic) can quickly cause unexpected stalls, but there is no arithmetic here: the only inherent difference between 8-bit and 32-bit code is the use of `movzx` versus `mov` in a memory load, which to the best of my knowledge are equivalent in performance (modulo store-to-load forwarding and such).
It does look like LLVM generates ugly code for address calculation, though. I don't think it's necessarily going to cause a large effect, since `movzx r8d, cl; lea rdx, [rdi + r8]` doesn't depend on the previous iteration, and the critical latency chain here is just `inc rax`, but it might affect throughput a little. It's quite unfortunate that the induction heuristics didn't do their best here.
> so there's an `asm volatile` style fence
I've seen a couple people suggest the compiler fence, and all of them did the same thing -- they put `asm volatile` after the assignment to `j`, not before. Could you explain your thought process here? I thought putting it before the second load would make more sense, because I can imagine
if (j != next_j[i][j]) {
j = next_j[i][j];
asm volatile("" : "+r"(j));
}
being (de)optimized to unsigned char value = next_j[i][j];
_Bool flag = j != value;
j = value;
if (flag) {
asm volatile("" : "+r"(j));
}
which in turn could be compiled to `mov reg, [mem]; cmp reg, reg; mov reg, reg; je`, whereas if (j != next_j[i][j]) {
asm volatile("" : "+r"(j));
j = next_j[i][j];
}
doesn't permit such an optimization because it forces a conditional load, which you can't avoid putting behind a branch.I have no doubt that putting the fence after the assignment works in this scenario (clearly it does, since LLVM and GCC recognize it), but I don't intuitively see why.