我正在优化c ++代码。
在一个关键步骤,我想实现以下函数y=f(x)
:
f(0)=1
f(1)=2
f(2)=3
f(3)=0
哪一个更快?使用查找表或i=(i+1)&3
或i=(i+1)%4
?或者更好的建议?
答案 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);”