Why bitoperation and multiplication is prefered here over a condition?
I found this bit operation in a source code:
A = 0b0001;
B = 0b0010;
C = 0b0100;
flags |= !!(flags & (A | B)) * C;
I can't see, why this complicated expression is used. flags & (A | B)
filters flags
to A | B
A | B
. Now it's converted to true
, if flags is set to anything, and false
otherwise. true * C == C
and false * C == 0
. Is it slower to just use flags = flags ? flags | C
flags = flags ? flags | C
flags = flags ? flags | C
?
On GCC 7.3 it compiles to
bitwise:
mov eax, edi
mov edx, edi
or edx, 4
test al, 3
cmovne eax, edx
ret
condition:
mov eax, edi
mov edx, 0
or eax, 4
test edi, edi
cmove eax, edx
ret
Clang 6.0 also eliminate the redundant call:
bitwise:
xor eax, eax
test dil, 3
setne al
shl eax, 2
or eax, edi
ret
condition:
mov eax, edi
or eax, 4
test edi, edi
cmove eax, edi
ret
Do I oversee something?
You left out the test with A|B
in your version (spotted by MichaelPetch). And you're zeroing flags
instead of leaving it unmodified if the test fails. Remember there can be other set flags in other bits which need to be unaffected by setting a flag C
that's required/implied if either of A
or B
are present. (See the comment in the original source you linked: Add TRANSFER_BIT if missing (implied)
)
And equivalent ternary operator to just replace the boolean-multiply hack would be flags |= (flags & (A|B)) ? C : 0
flags |= (flags & (A|B)) ? C : 0
.
Another way to do it is flags = (flags & (A|B)) ? flags|C : flags
flags = (flags & (A|B)) ? flags|C : flags
, and that compiles even better.
I fixed your code on Godbolt to use a correct expression, and yes it compiles to better-looking asm with gcc/clang, and MSVC. Probably that would still be true in the context of that larger function that does more stuff with flags
.
It looks like this version compiles the best with gcc/clang/MSVC:
int condition(int flags) {
flags = (flags&(A|B)) ? flags | C : flags;
return flags;
}
; gcc/clang both emit
mov eax, edi
or eax, 4
test dil, 3
cmove eax, edi
ret
And MSVC is similar, but with an and
instead of test
, and an extra mov
because of the destructive and
.
Clang 6.0 also eliminate the redundant call
What call
? There are no function calls in this code. Did you mean clang avoids the redundant mov edx, edi
that gcc uses? Yeah, gcc is dumb there.
Why would anyone write it the way they did?
Perhaps their past experience was with older compilers that didn't do a good job with the ternary operator, or with non-x86 platforms without a cmov
and actually using a *
operator in the source was one of the only ways to get branchless asm.
My guess is whoever wrote that picked up this trick a while ago, and still uses it instead of a ternary operator. It's not a good idea, and doesn't lead to better code.
链接地址: http://www.djcxy.com/p/15072.html