是i =(i + 1)& 3比i =(i + 1)%4快

时间:2011-12-05 21:25:01

标签: c++ micro-optimization

我正在优化c ++代码。 在一个关键步骤,我想实现以下函数y=f(x)

f(0)=1

f(1)=2

f(2)=3

f(3)=0

哪一个更快?使用查找表或i=(i+1)&3i=(i+1)%4?或者更好的建议?

6 个答案:

答案 0 :(得分:16)

几乎可以肯定,查找表最慢。在很多情况下,编译器将为(i+1)&3(i+1)%4生成相同的程序集;但是,根据i的类型/签名,它们可能不是严格等同的,编译器将无法进行优化。例如代码

int foo(int i)
{
    return (i+1)%4;
}

unsigned bar(unsigned i)
{
    return (i+1)%4;
}

在我的系统上,gcc -O2生成:

0000000000000000 <foo>:
   0:   8d 47 01                lea    0x1(%rdi),%eax
   3:   89 c2                   mov    %eax,%edx
   5:   c1 fa 1f                sar    $0x1f,%edx
   8:   c1 ea 1e                shr    $0x1e,%edx
   b:   01 d0                   add    %edx,%eax
   d:   83 e0 03                and    $0x3,%eax
  10:   29 d0                   sub    %edx,%eax
  12:   c3                      retq   

0000000000000020 <bar>:
  20:   8d 47 01                lea    0x1(%rdi),%eax
  23:   83 e0 03                and    $0x3,%eax
  26:   c3                      retq

因为您可以看到由于有关有符号模数结果的规则,(i+1)%4首先会生成更多代码。

最重要的是,如果表达了你想要的东西,你可能最好使用(i+1)&3版本,因为编译器做一些你不想要的事情的机会较少。

答案 1 :(得分:7)

我不会讨论过早优化。但答案是他们的速度会相同。

任何理智的编译器都会将它们编译为同一个东西。无论如何,除以2的幂的除法/模数将被优化为按位运算。

因此,请使用您找到的(或其他人会发现的)更具可读性。

编辑:正如罗兰所指出的那样,它有时会根据符号表现不同:

未签名&amp;:

int main(void)
{
    unsigned x;
    cin >> x;
    x = (x + 1) & 3;
    cout << x;

    return 0;
}

mov eax, DWORD PTR _x$[ebp]
inc eax
and eax, 3
push    eax

无符号模数:

int main(void)
{
    unsigned x;
    cin >> x;
    x = (x + 1) % 4;
    cout << x;

    return 0;
}

mov eax, DWORD PTR _x$[ebp]
inc eax
and eax, 3
push    eax

已签名&amp;:

int main(void)
{
    int x;
    cin >> x;
    x = (x + 1) & 3;
    cout << x;

    return 0;
}

mov eax, DWORD PTR _x$[ebp]
inc eax
and eax, 3
push    eax

签名模数:

int main(void)
{
    int x;
    cin >> x;
    x = (x + 1) % 4;
    cout << x;

    return 0;
}

mov eax, DWORD PTR _x$[ebp]
inc eax
and eax, -2147483645            ; 80000003H
jns SHORT $LN3@main
dec eax
or  eax, -4                 ; fffffffcH

答案 2 :(得分:5)

很有可能,您不会发现任何差异:任何合理的现代编译器都知道将两者优化为相同的代码。

答案 3 :(得分:3)

您是否尝试过基准测试?作为一个随意的猜测,我假设&3版本会更快,因为这是一个简单的加法和按位AND操作,这两个操作都应该是任何现代CPU上的单周期操作。

%4可以采用几种不同的方式,具体取决于编译器的智能程度。它可以通过除法来完成,它比加法慢得多,或者它也可以转换为按位and操作,最终和&3版本一样快。

答案 4 :(得分:0)

与Mystical相同,但C和ARM

int fun1 ( int i )
{
    return( (i+1)&3 );
}

int fun2 ( int i )
{
    return( (i+1)%4 );
}

unsigned int fun3 ( unsigned int i )
{
    return( (i+1)&3 );
}

unsigned int fun4 ( unsigned int i )
{
    return( (i+1)%4 );
}

创建:

00000000 <fun1>:
   0:   e2800001    add r0, r0, #1
   4:   e2000003    and r0, r0, #3
   8:   e12fff1e    bx  lr

0000000c <fun2>:
   c:   e2802001    add r2, r0, #1
  10:   e1a0cfc2    asr ip, r2, #31
  14:   e1a03f2c    lsr r3, ip, #30
  18:   e0821003    add r1, r2, r3
  1c:   e2010003    and r0, r1, #3
  20:   e0630000    rsb r0, r3, r0
  24:   e12fff1e    bx  lr

00000028 <fun3>:
  28:   e2800001    add r0, r0, #1
  2c:   e2000003    and r0, r0, #3
  30:   e12fff1e    bx  lr

00000034 <fun4>:
  34:   e2800001    add r0, r0, #1
  38:   e2000003    and r0, r0, #3
      3c:   e12fff1e    bx  lr

对于负数,掩码和模数不等效,仅适用于正数/无符号数。对于这些情况,您的编译器应该知道%4与&amp; 3相同,并使用较低的on(&amp; 3)作为上面的gcc。 clang / llc下面

fun3:                             
    add r0, r0, #1
    and r0, r0, #3
    mov pc, lr

fun4:
    add r0, r0, #1
    and r0, r0, #3
    mov pc, lr

答案 5 :(得分:-1)

当然&amp;比%快。许多以前的帖子证明了这一点。同样,因为我是局部变量,你可以使用++ i而不是i + 1,因为大多数编译器都可以更好地实现它。 i + 1可以(不)优化为++ i。

更新:也许我不清楚,我的意思是,该功能应该只是“返回((++ i)&amp; 3);”