现代C编译器可以优化位访问的组合吗?

时间:2014-06-30 17:42:03

标签: c optimization compiler-construction bit-shift

如果设置var的第1,3,5,7,9,11,13或15位中的一个,我希望FALSE不等input。< / p>

一个似乎相当常见的解决方案是:

int var = 1 & (input >>  1) ||
          1 & (input >>  3) ||
          1 & (input >>  5) ||
          1 & (input >>  7) ||
          1 & (input >>  9) ||
          1 & (input >> 11) ||
          1 & (input >> 13) ||
          1 & (input >> 15);

但是,我担心这会导致编译器生成不必要的长代码。

以下代码也会产生所需的结果。它会更有效率吗?

int var = input & 0b1010101010101010;

谢谢!

5 个答案:

答案 0 :(得分:3)

您的第二个示例不等同于

你想要的是(使用非标准的二进制文字):

int var = !!(input & 0b1010101010101010));

或者使用hex-literals(那些是标准的):

int var = !!(input & 0xaaaa));

更改:使用十六进制常量和双重否定(相当于!= 0) 这预先假定input不是volatile,也不是原子类型。

一个好的编译器应该优化两个相同的指令(大多数现代编译器都足够好)。

最后,测试和测量,大多数编译器将输出生成的汇编代码,你甚至不需要反汇编程序!

答案 1 :(得分:2)

如果input是易失性的,如果设置了第1位,编译器将需要读取一次,第1位的两次清除但设置为3,如果第1位和第3位清除但是5则是编译器可能有办法优化代码进行单独的位测试,但必须分别测试这些位。

如果input不是易失性的,编译器可以优化代码,但我不会特别期望它。我希望任何编译器,无论多么古老,都要优化

int var = (input & (
  (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) |
  (1 << 9) | (1 << 11) | (1 << 13) | (1 << 15)
) != 0);

这似乎是你所追求的。

答案 2 :(得分:1)

它将取决于处理器以及可用的指令,以及优化编译器的优点。我怀疑在你的情况下,这些代码行中的任何一行都会编译成相同的指令。

但我们可以做得比怀疑更好,你可以自己检查。使用gcc,使用-S编译器标志让它输出它生成的程序集。然后你可以自己比较一下。

答案 3 :(得分:1)

正统的解决方案应该是使用遗忘的位域来映射你的标志,比如

struct
{
  bool B0: 1;
  bool B1: 1;
  bool B2: 1;
  bool B3: 1;
  bool B4: 1;
  bool B5: 1;
  bool B6: 1;
  bool B7: 1;
  bool B8: 1;
  bool B9: 1;
  bool B10: 1;
  bool B11: 1;
  bool B12: 1;
  bool B13: 1;
  bool B14: 1;
  bool B15: 1;
} input;

并使用表达式

bool Var= input.B1 || input.B3 || input.B5 || input.B7 || input.B9 || input.B11 || input.B13 || input.B15;

我怀疑优化编译器会使用单程掩码技巧,但老实说我还没有尝试过。

答案 4 :(得分:1)

处理效果取决于编译器。

我测试了此代码的一个小变体:

int test(int input) {
  int var = 1 & (input >>  1) ||
      1 & (input >>  3) ||
      1 & (input >>  5) ||
      1 & (input >>  7) ||
      1 & (input >>  9) ||
      1 & (input >> 11) ||
      1 & (input >> 13) ||
      1 & (input >> 15);
  return var != 0;
}

结果

对于x64,全部使用-O2编译

GCC

xor eax, eax
and edi, 43690
setne   al
ret

非常好。这正是你所希望的转变。

锵:

testw   $10922, %di             # imm = 0x2AAA
movb    $1, %al
jne .LBB0_2
andl    $32768, %edi            # imm = 0x8000
shrl    $15, %edi
movb    %dil, %al
.LBB0_2:
movzbl  %al, %eax
ret

是的,这有点奇怪。大多数测试都在一起进行..除了一个。我认为没有理由这样做,也许其他人可以对此有所了解。

真正的惊喜,ICC:

    movl      %edi, %eax                                    #7.32
    movl      %edi, %edx                                    #8.26
    movl      %edi, %ecx                                    #9.26
    shrl      $1, %eax                                      #7.32
    movl      %edi, %esi                                    #10.26
    shrl      $3, %edx                                      #8.26
    movl      %edi, %r8d                                    #11.26
    shrl      $5, %ecx                                      #9.26
    orl       %edx, %eax                                    #7.32
    shrl      $7, %esi                                      #10.26
    orl       %ecx, %eax                                    #7.32
    shrl      $9, %r8d                                      #11.26
    orl       %esi, %eax                                    #7.32
    movl      %edi, %r9d                                    #12.25
    orl       %r8d, %eax                                    #7.32
    shrl      $11, %r9d                                     #12.25
    movl      %edi, %r10d                                   #13.25
    shrl      $13, %r10d                                    #13.25
    orl       %r9d, %eax                                    #7.32
    shrl      $15, %edi                                     #14.25
    orl       %r10d, %eax                                   #7.32
    orl       %edi, %eax                                    #7.32
    andl      $1, %eax                                      #7.32
    ret                                                     #15.21

好的,所以它优化了一点 - 没有分支,1 &&#39;被卷起来。但这令人失望。

结论

您的里程可能会有所不同。为了安全起见,您当然可以直接使用简单版本,而不是依赖于编译器。