C++ c++;与可读性更强的方法相比
我最近正在为一个研究项目编写一些代码,在这个项目中效率非常重要。我一直在考虑删除一些常规方法,改用按位异或。我想知道的是,如果有不同(如果我执行这个操作数百万次),或者在g++中使用03之后,它是否会相同,这是否会产生影响 我想到的两个例子是: 我有一个例子(我正在处理纯正整数),如果n是奇数,我需要将n改为n-1,如果n是偶数,我需要将n改为(n+1)。我想我有几个选择:C++ c++;与可读性更强的方法相比,c++,performance,bitwise-operators,C++,Performance,Bitwise Operators,我最近正在为一个研究项目编写一些代码,在这个项目中效率非常重要。我一直在考虑删除一些常规方法,改用按位异或。我想知道的是,如果有不同(如果我执行这个操作数百万次),或者在g++中使用03之后,它是否会相同,这是否会产生影响 我想到的两个例子是: 我有一个例子(我正在处理纯正整数),如果n是奇数,我需要将n改为n-1,如果n是偶数,我需要将n改为(n+1)。我想我有几个选择: if(n%2) // or (n%2==0) and flip the order n=n-1 else
if(n%2) // or (n%2==0) and flip the order
n=n-1
else
n=n+1
或
最后:
n=n^1;
所有的方法显然都是一样的,但我觉得第三种方法是最有效的
下一个例子是更一般的说明。假设我比较两个正整数,其中一个会比其他的好吗。或者,即使我执行此操作数百万次,差异是否真的不明显:
if(n_1==n_2)
if(! (n_1 ^ n_2) )
if( n_1 ^ n_2) else \do work here
编译器会在所有这些实例中执行相同的操作吗?我只是好奇是否有这样一个实例,我应该使用位操作,而不信任编译器为我做这项工作
修正:在正确的问题陈述中
如果n是偶数,我需要将n改为n-1;如果n是奇数,我需要将n改为(n+1)
在这种情况下,不管效率如何,n=n^1
都是错误的
对于第二种情况,==
将与其他任何情况一样高效(如果不是更高效的话)
一般来说,当涉及到优化时,您应该自己进行基准测试。如果一个潜在的优化不值得进行基准测试,那么它就不值得进行。一个好的编译器将优化
n%2
,但您可以随时检查生成的程序集以查看。如果您看到divide,请自己开始优化它,因为divide的速度非常慢。您应该信任您的编译器。gcc/++是多年开发的产物,它能够进行您可能正在考虑的任何优化。而且,如果你开始胡闹,你很可能会干扰它优化代码的努力。检查起来很容易,只需启动反汇编程序。看一看:
f、 c:
构建和分解:
$ cc -O3 -c f.c
$ otool -tV f.o
f.o:
(__TEXT,__text) section
_f1:
00 pushq %rbp
01 movq %rsp,%rbp
04 xorl $0x01,%edi
07 movl %edi,%eax
09 leave
0a ret
0b nopl _f1(%rax,%rax)
_f2:
10 pushq %rbp
11 movq %rsp,%rbp
14 leal 0xff(%rdi),%eax
17 leal 0x01(%rdi),%edx
1a andl $0x01,%edi
1d cmovel %edx,%eax
20 leave
21 ret
看起来
f1()
稍微短了一点,实际上这是否重要取决于一些基准测试。关于确定的唯一方法是测试。我必须同意,需要一个相当聪明的编译器才能产生高效的输出:
if(n%2) // or (n%2==0) and flip the order
n=n-1
else
n=n+1
正如它可以为n^=1
,但我最近还没有检查过任何类似的东西,所以不能肯定地说
至于你的第二个问题,我怀疑这有什么区别——对于这些方法中的任何一种,平等性比较都会很快结束。如果您想要提高速度,最主要的是避免涉及分支,例如:
if (a == b)
c += d;
可以写成:c+=d*(a==b)代码>。看看汇编语言,第二种语言通常看起来有点凌乱(使用丑陋的粗糙度将比较结果从标志中获取到普通寄存器),但通过避免任何分支,第二种语言的性能通常会更好
编辑:至少我手头的编译器(gcc和MSVC)不会为if
生成cmov
,但会为*(a==b)
生成sete
。我将代码扩展为可测试的内容
Edit2:由于Potatoswatter提出了另一种可能性,即使用位and代替乘法,所以我决定和其他方法一起进行测试。以下是添加的代码:
#include <time.h>
#include <iostream>
#include <stdlib.h>
int addif1(int a, int b, int c, int d) {
if (a==b)
c+=d;
return c;
}
int addif2(int a, int b, int c, int d) {
return c += d * (a == b);
}
int addif3(int a, int b, int c, int d) {
return c += d & -(a == b);
}
int main() {
const int iterations = 50000;
int x = rand();
unsigned tot1 = 0;
unsigned tot2 = 0;
unsigned tot3 = 0;
clock_t start1 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot1 +=addif1(i, j, i, x);
}
clock_t stop1 = clock();
clock_t start2 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot2 +=addif2(i, j, i, x);
}
clock_t stop2 = clock();
clock_t start3 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot3 +=addif3(i, j, i, x);
}
clock_t stop3 = clock();
std::cout << "Ignore: " << tot1 << "\n";
std::cout << "Ignore: " << tot2 << "\n";
std::cout << "Ignore: " << tot3 << "\n";
std::cout << "addif1: " << stop1-start1 << "\n";
std::cout << "addif2: " << stop2-start2 << "\n";
std::cout << "addif3: " << stop3-start3 << "\n";
return 0;
}
使用&
而不是*
,会带来一定的改善——几乎与*
在的情况下所带来的改善一样多。使用gcc,结果会有很大不同:
Ignore: 2680875904
Ignore: 2680875904
Ignore: 2680875904
addif1: 2901
addif2: 2886
addif3: 7675
在这种情况下,使用if
的代码比使用*
的代码的速度要快得多,但是使用&
的代码比任何一种都慢——慢得多!如果有人关心的话,我发现这一点非常令人惊讶,我用不同的标志重新编译了几次,用每个标志重新运行了几次,以此类推,结果是完全一致的——使用&
的代码始终相当慢
使用gcc编译的代码的第三个版本的糟糕结果让我们回到我说的开始[并结束此编辑]:
正如我在开始时所说,“唯一确定的方法是测试”——但至少在这个有限的测试中,乘法始终优于if
。可能存在编译器、编译器标志、CPU、数据模式、迭代计数等的一些组合,这有利于if
而不是乘法——毫无疑问,差异小到足以使测试完全可信。尽管如此,我相信这是一种值得了解的技术;对于主流编译器和CPU来说,它似乎相当有效(尽管MSVC肯定比gcc更有用)
[恢复编辑2:]gcc使用&
的结果表明:1)微优化可以/是特定于编译器的,2)实际结果与预期有多大不同。是n^=1
比if(n%2)-n快;else++n代码>?对我不希望编译器会优化它。由于按位操作要简洁得多,因此可能需要熟悉XOR,并在该行代码上添加注释
如果它真的对程序的功能至关重要,那么它也可以被认为是一个可移植性问题:如果你在你的编译器上测试并且速度很快,那么当你在另一个编译器上尝试时,你可能会感到惊讶。通常,这不是代数优化的问题
#include <time.h>
#include <iostream>
#include <stdlib.h>
int addif1(int a, int b, int c, int d) {
if (a==b)
c+=d;
return c;
}
int addif2(int a, int b, int c, int d) {
return c += d * (a == b);
}
int addif3(int a, int b, int c, int d) {
return c += d & -(a == b);
}
int main() {
const int iterations = 50000;
int x = rand();
unsigned tot1 = 0;
unsigned tot2 = 0;
unsigned tot3 = 0;
clock_t start1 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot1 +=addif1(i, j, i, x);
}
clock_t stop1 = clock();
clock_t start2 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot2 +=addif2(i, j, i, x);
}
clock_t stop2 = clock();
clock_t start3 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot3 +=addif3(i, j, i, x);
}
clock_t stop3 = clock();
std::cout << "Ignore: " << tot1 << "\n";
std::cout << "Ignore: " << tot2 << "\n";
std::cout << "Ignore: " << tot3 << "\n";
std::cout << "addif1: " << stop1-start1 << "\n";
std::cout << "addif2: " << stop2-start2 << "\n";
std::cout << "addif3: " << stop3-start3 << "\n";
return 0;
}
Ignore: 2682925904
Ignore: 2682925904
Ignore: 2682925904
addif1: 4814
addif2: 3504
addif3: 3021
Ignore: 2680875904
Ignore: 2680875904
Ignore: 2680875904
addif1: 2901
addif2: 2886
addif3: 7675