Using GCC/LLVM on MacOS X, and 64-bit compilation, and generating assembler with:
gcc -S -Os clamp.c
where clamp.c
contains:
typedef unsigned char BYTE;
BYTE Clamp_1(int n)
{
n &= -(n >= 0);
return n | ((255 - n) >> 31);
}
BYTE Clamp_2(int n)
{
if (n > 255)
n = 255;
else if (n < 0)
n = 0;
return n;
}
The assembler for the two functions (with prologue and epilogue) is:
.section __TEXT,__text,regular,pure_instructions
.globl _Clamp_1
_Clamp_1:
Leh_func_begin1:
pushq %rbp
Ltmp0:
movq %rsp, %rbp
Ltmp1:
movl %edi, %eax
shrl $31, %eax
xorl $1, %eax
negl %eax
andl %edi, %eax
movl $255, %ecx
subl %eax, %ecx
sarl $31, %ecx
orl %eax, %ecx
movzbl %cl, %eax
popq %rbp
ret
Leh_func_end1:
.globl _Clamp_2
_Clamp_2:
Leh_func_begin2:
pushq %rbp
Ltmp2:
movq %rsp, %rbp
Ltmp3:
cmpl $256, %edi
jl LBB2_2
movl $255, %edi
jmp LBB2_4
LBB2_2:
testl %edi, %edi
jns LBB2_4
xorl %edi, %edi
LBB2_4:
movzbl %dil, %eax
popq %rbp
ret
Leh_func_end2:
The pushq
, popq
and ret
are the function call overhead. Your code (Clamp_1()
) assembles to 11 instructions; mine to 9 (but there are two jumps in mine, which might wreak havoc on pipelined execution). Neither approaches the 7 instructions in your optimized version.
Interestingly, though, when I use GCC 4.6.1 on the same code, the assembler output is:
.text
.globl _Clamp_1
_Clamp_1:
LFB0:
movl %edi, %eax
movl $255, %edx
notl %eax
sarl $31, %eax
andl %edi, %eax
subl %eax, %edx
sarl $31, %edx
orl %edx, %eax
ret
LFE0:
.globl _Clamp_2
_Clamp_2:
LFB1:
xorl %edx, %edx
testl %edi, %edi
movl $255, %eax
cmovns %edi, %edx
cmpl $255, %edx
cmovle %edx, %eax
ret
LFE1:
Now I see 8 instructions in Clamp_1
and 6 in Clamp_2
apart from the ret
.
Further experimentation shows that there is a difference in the output between gcc -Os -S clamp.c
and gcc -S -Os clamp.c
; the former generates the optimized (smaller) outputs; the latter generates the more verbose output.
(sizeof(int)*CHAR_BIT)-1
. You're correct of course, but I'm unlikely to use this code on any platform where anint
isn't 32 bits. \$\endgroup\$