I'm working with a slightly modified loop that counts down rather than up, so that the loop has slightly fewer instructions. I've switched to using an "add to memory" to reduce the instruction count further. None of these seem to directly affect the speed of the loop, but here's the fastest loop I have:
.p2align 3
1:
addq %rax, counter(%rip)
dec %rax
jmp 2f
2:
jne 1b
rep ret
On Skylake, it executes in 0.497 s. If I remove the "jump to the next instruction", it slows to 0.646 s. This agrees with your explanation. But here's the part I don't understand, and that may undercut your theory: if I change the first line to be ".p2align 4" (that is, if I increase the minimum alignment from 8 to 16) the speed is the same whether or not I have the extra jump. Even more confusingly, it's always the slower speed!Here's what port usage looks like for the two different cases (both with the same instructions, just different alignments):
Fast Slow
INSTR_RETIRED_ANY | 1600095750 | 1600095846
CPU_CLK_UNHALTED_CORE | 1643927320 | 2192886305
UOPS_DISPATCHED_PORT_PORT_0 | 645482071 | 774370373
UOPS_DISPATCHED_PORT_PORT_1 | 653347597 | 782702101
UOPS_DISPATCHED_PORT_PORT_2 | 299084760 | 298170998
UOPS_DISPATCHED_PORT_PORT_3 | 300474456 | 292800108
UOPS_DISPATCHED_PORT_PORT_4 | 1410210909 | 1994332142
UOPS_DISPATCHED_PORT_PORT_5 | 406138100 | 791887776
UOPS_DISPATCHED_PORT_PORT_6 | 989257513 | 1338919000
UOPS_DISPATCHED_PORT_PORT_7 | 200496146 | 209093087
From the first line we see that both Fast and Slow have the same number of instructions executed (which makes sense, since both are executing the same instructions), but down below we see very different numbers of µops dispatched! I'm not sure what to make of this, but I think this shows that it is not (solely?) a front end issue.