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

上一篇: c ++二维数组访问速度基于[a] [b]顺序变化?

下一篇: 为什么比特操作和乘法在这里优先于一个条件?