The talk above about conditional moves and such caused me to explore a bit more, and I found out that
#define DEFINE_MEDIAN3(name, type) \
type name(const type a, const type b, const type c) { \
const type a1 = (a < b) ? a : b, \
b1 = (a < b) ? b : a; \
const type c1 = (a1 < c) ? c : a1; \
return (b1 < c1) ? b1 : c1; \
}
DEFINE_MEDIAN3(Median3d, double)
DEFINE_MEDIAN3(Median3f, float)
DEFINE_MEDIAN3(Median3l, long)
DEFINE_MEDIAN3(Median3i, int)
DEFINE_MEDIAN3(Median3c, char)
DEFINE_MEDIAN3(Median3s, short)generates near-optimal conditional move assembly as previously discussed, for x86-64 on both gcc and clang. char and short tends to generate additional code on older versions, but clang 9.0 to 15.0 generates the optimal (AFAICS) code:
Median3d: # @Median3d
movapd xmm3, xmm0
minsd xmm3, xmm1
maxsd xmm1, xmm0
maxsd xmm2, xmm3
minsd xmm1, xmm2
movapd xmm0, xmm1
ret
Median3f: # @Median3f
movaps xmm3, xmm0
minss xmm3, xmm1
maxss xmm1, xmm0
maxss xmm2, xmm3
minss xmm1, xmm2
movaps xmm0, xmm1
ret
Median3l: # @Median3l
cmp rdi, rsi
mov rax, rsi
cmovle rax, rdi
cmovl rdi, rsi
cmp rax, rdx
cmovl rax, rdx
cmp rdi, rax
cmovle rax, rdi
ret
Median3i: # @Median3i
cmp edi, esi
mov eax, esi
cmovle eax, edi
cmovl edi, esi
cmp eax, edx
cmovl eax, edx
cmp edi, eax
cmovle eax, edi
ret
Median3c: # @Median3c
cmp dil, sil
mov eax, esi
cmovle eax, edi
cmovl edi, esi
cmp al, dl
cmovl eax, edx
cmp dil, al
cmovle eax, edi
ret
Median3s: # @Median3s
cmp di, si
mov eax, esi
cmovle eax, edi
cmovl edi, esi
cmp ax, dx
cmovl eax, edx
cmp di, ax
cmovle eax, edi
retGCC 8.5 to 12.2 tends to generate the 9-instruction form with conditional jumps for floats and doubles, with -O2 generating better code (fewer jumps) than -Os:
Median3d:
comisd xmm1, xmm0
ja .L2
movapd xmm3, xmm1
movapd xmm1, xmm0
movapd xmm0, xmm3
.L2:
maxsd xmm2, xmm0
minsd xmm1, xmm2
movapd xmm0, xmm1
ret
Median3f:
comiss xmm1, xmm0
ja .L7
movaps xmm3, xmm1
movaps xmm1, xmm0
movaps xmm0, xmm3
.L7:
maxss xmm2, xmm0
minss xmm1, xmm2
movaps xmm0, xmm1
ret
Median3l:
cmp rsi, rdi
mov rax, rdi
cmovle rax, rsi
cmp rax, rdx
cmovl rax, rdx
cmp rsi, rdi
cmovl rsi, rdi
cmp rax, rsi
cmovg rax, rsi
ret
Median3i:
cmp esi, edi
mov eax, edi
cmovle eax, esi
cmp eax, edx
cmovl eax, edx
cmp esi, edi
cmovl esi, edi
cmp eax, esi
cmovg eax, esi
ret
Median3c:
cmp sil, dil
mov eax, edi
cmovle eax, esi
cmp al, dl
cmovl eax, edx
cmp sil, dil
cmovl esi, edi
cmp al, sil
cmovg eax, esi
ret
Median3s:
cmp si, di
mov eax, edi
cmovle eax, esi
cmp ax, dx
cmovl eax, edx
cmp si, di
cmovl esi, edi
cmp ax, si
cmovg eax, esi
retIt looks like GCC optimization "fails" because it switches too early from conditional loads to conditional jumps; that clang has better optimization for this kind of comparison trees.