为什么在这种条件下优先选择比特操作和乘法?

时间:2018-03-12 17:28:53

标签: c assembly x86 bitwise-operators micro-optimization

我在源代码中找到了这个位操作:

A = 0b0001;
B = 0b0010;
C = 0b0100;

flags |= !!(flags & (A | B)) * C;

我无法看到,为什么要使用这种复杂的表达方式。 flags & (A | B)过滤flagsA | B。如果标志设置为任何内容,它现在转换为true,否则转换为falsetrue * C == Cfalse * C == 0。使用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

我是否监督某事?

1 个答案:

答案 0 :(得分:4)

您在版本中A|B遗漏了测试flags。如果测试失败,您将对C进行归零,而不是将其保留为未修改状态。请记住,如果AB中存在Add TRANSFER_BIT if missing (implied)flags |= (flags & (A|B)) ? C : 0,则可以通过设置标记flags = (flags & (A|B)) ? flags|C : flags来确定其他位中的其他设置标志是否不受影响。 (请参阅您关联的spotted by MichaelPetch中的评论:flags

只需替换boolean-multiply hack的等效三元运算符为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,编译得更好。

我在Godbolt上修改了你的代码以使用正确的表达式,是的,它用gcc / clang和MSVC编译成更好看的asm。可能在使用test做更多内容的更大函数的情况下仍然如此。

看起来像这个版本the original source

mov

与MSVC类似,但and代替call,而mov edx, edi因为破坏性cmov而增加*

  

Clang 6.0还消除了冗余呼叫

$ rails db:drop db:create db:migrate是什么?此代码中没有函数调用。你的意思是clang避免了gcc使用的冗余 let session = await authenticationService.createSessionByEmailPassword(model.email, model.oldpassword), // <--- Note comma here secuity_ok = !!session; 吗?是的,gcc在那里很蠢。

为什么有人会像他们那样写它?

也许他们过去的经验是旧的编译器没有用三元运算符做得很好,或者没有secuity_ok的非x86平台并且实际上使用let运算符源是获得无分支asm的唯一方法之一。

我的猜测是不久之前曾写过这个技巧的人,并且仍然使用它而不是三元运算符。这不是一个好主意,并且没有导致更好的代码。