https://gcc.gnu.org/bugzilla/show_bug.cgi?id=125970

--- Comment #3 from cuilili <lili.cui at intel dot com> ---
For option -O2 -mtune=generic -march=x86-64-v3, commit causes a ~15% regression
on the HINT benchmark. The commit converts the branch inc = (errio < errjo) + 1
in the hot loop into a cmov/setcc sequence.

HINT's hot loop is memory-bound ("load a batch → little FP math → store a
batch"), so its performance depends on MLP — keeping many memory accesses in
flight so their latencies overlap.

Branch version: the branch is well-predicted, so the CPU speculates past the
compare and computes these addresses early → memory accesses issue ahead of
time → high MLP, latencies hidden.

cmov version: the cmov sequence itself is not expensive, but the selected value
is used directly as a memory address index for the following accesses, it
cannot be speculated. Address generation is pinned to the critical path
(compare → setcc → cmov → lea), so the dependent accesses can no longer issue
early. Overlap drops and MLP collapses.

cmov version:

comisd  %xmm3,%xmm1           
seta    %r10b                 
cmova   %r12d,%r15d           
movzbl  %r10b,%r10d  --- %r10 is used as an array index for the following
stores.        
lea     0x1(%r10,%rsi,1),%r10d --- r10 ← r10+rsi+1  = index A
movsd   %xmm3,(%r9,%r10,8)     --- store, uses index A
mov     %r11d,0x0(%rbp,%r10,4) --- store, uses index A
lea     (%rsi,%r15,1),%r10d    --- r10 ← rsi+r15    = index B, new r10, break
the dependency
movsd   %xmm1,(%r9,%r10,8)     --- store, uses index B
mov     %ecx,0x0(%rbp,%r10,4)  --- store, uses index B
mov     %esi,%r10d    ---r10 ← esi  new r10,breaks the dependency.         
movsd   0x8(%rax),%xmm9
mulsd   %xmm2,%xmm1
ucomisd 0x10(%r9,%r10,8),%xmm4
setp    %r10b                 
movzbl  %r10b,%r10d           
cmovne  %r12d,%r10d           
add     %esi,%r10d 
-----------------------------------------------------------------------    

It is hard to add a heuristic:

When the cmov's result feeds address computation, a misprediction is also more
expensive than usual (speculative memory accesses on the wrong path must be
squashed and redone). It is a trade-off that depends on predictability — cmov
wins when the branch is poorly predicted, the branch wins when it is
well-predicted — and static analysis cannot decide it without profile data.

Root limitation: static prediction cannot reflect real run-time behavior.

GCC's static branch-prediction probability for this branch:

if (errio < errjo)
    goto <bb 7>; [50.00%]
else
    goto <bb 6>; [50.00%]

GCC estimate: 50% / 50%
Reality (perf): 9.53% / 90.47% taken ratio — the branch is highly predictable.
(~99% correctly predicted).

Reply via email to