CUDA程序优化心得之串行优化

相信大家看到这个题目会有所疑问,毕竟要说的CUDA程序优化,而这里说的是串行程序的优化,我要说的是串行程序优化的能力对于并行程序优化同等的重要,因为大多数时候我们是从串行程序到并行程序,而且并行线程的内部依旧是串行的。
一般而言,算法级别的优化是最有效的,但是本文的优化并不涉及,本文假设你已经有了一个可以运行并能够得到正确结果的程序,在此基础上进行优化。

3.1 编译器选项
这是最简单的一种优化方法,一般而言使用高级的优化选项总比使用低优化选项的速度要快一些,如gcc就有o0,o1,o2,o3三个优化选项,一般使用o2就已经足够,注意有时03优化选项会更慢甚至产生错误的结果。

3.2 缓存优化

程序访问的数据在空间和时间上都有其局部性,也就是说一个数据被访问了,它附近的数据也很有可能被访问,因此,如果我们在访问下一个数据的时候,其已经在缓存中也就是说缓存命中,此时就用不着从内存中取数据,可加速访问。
比如有一个二维数组a[M][N],大家觉得下面的代码,那个快呢?

for(int I = 0; I < M; i++)
	for(int j = 0; j < N; j++)
		a[i][j] *= 3;

代码二、
for(int I = 0; I < M; i++)
	for(int j = 0; j < M; j++)
		a[j][i] *= 3;

当然还有其它一些优化缓存的方法,比如分块等。缓存的优化,往往对程序的性能有决定性的改变。

3.2 选用尽量小的数据类型
一般而言,小数据类型的计算速度比大数据类型要快
3.4 结构体声明
声明结构体时,尽量大数据类型在前,小数据类型在后。这样会节省一些空间,你可以自己验证一下,只要sizeof运算符就可以了。在作用CUDA时可以使用__align__(xx)来确定对齐的字节数。

3.5 使用位运算
因为位运算要比算术运算快,因此使用位运算取代算术运算是有利的。但是要注意的是由于GPU没有ECC,使用位运算的时候出错的概率有点大。
如乘以一个数可以转变成左移操作,如果乘数是常量,编译器会自动转换;除以2的幂可换成右移;模2的幂可换成位与。

3.6 使用复合运算符
在C中使用+=,-=,++,――等复合运算符会比较快。

3.7 展开小循环
展开小循环不但省略了每次的判断,更使用编译器使用向量化和并行指令成为可能。如下面的代码,代码一效率就比代码二高


代码一
float sum = 0.0f;
for(int I = 0; I <100000; I++){
Sum += a[I]; 

float sum = 0.0f, sum1 = 0.0f, sum2 = 0.0f, sum3 = 0.0f;
for(int I = 0; I < 25000; I +=4){
	Sum1 +=  a[I];
	Sum2 +=  a[I+1;
	Sum3 +=  a[I+2];
	Sum +=  a[I+3];
}
Sum += sum1+sum2+sum3;

3.8 表达式移除
假设有一向量v长为N;下面的代码一就比代码二快


代码一
Vector<int>  vi;
Int len = vi.size();
For(int I = 0; I < len; i++)
…
代码二
vector<int>  vi;
…
for(int i = 0; I < vi.size(); i++)
…. 

3.9 判断式
在C中判断式是短路的,也就是说如果现在的信息已经能够决定整体的结果,后面的就不用算了。如if(a && b),如果a为假,那么if里语句就一定不能执行,故b不用再求值,这样如果a,b中有一个是计算量相当大的,就应当将它放在后面,如果计算量差不多,就把a,b中为假概率大的放在前面。对于||可以类推。

3.10 查表
查表在图形学非常常用。比如有一批数据,我们要知道每个数据出现的次数,我们就没有必要每次都去统计,只要统计一次,然后记录结果,在每次查的时候,只要查记录的结果就行了。

其它
如声明float时加f后缀,使用const,static,少用虚函数等。

最后要说的是,很多时候编译器能够帮助我们优化,因此,这里说的优化方法在某些编译器下,或者某些编译器开启了某些选项的情况下,并不有效。

欢迎大家补充和提出意见,共同进步。

for(int I = 0; I < M; i++)
for(int j = 0; j < N; j++)
a[i][j] *= 3;

代码二、
for(int I = 0; I < M; i++)
for(int j = 0; j < M; j++)
a[j][i] *= 3;
能具体解释下这两个代码那个快么?

代码一
float sum = 0.0f;
for(int I = 0; I <100000; I++){
Sum += a[I];
代码二
float sum = 0.0f, sum1 = 0.0f, sum2 = 0.0f, sum3 = 0.0f;
for(int I = 0; I < 25000; I +=4){
Sum1 += a[I];
Sum2 += a[I+1; Sum3 += a[I+2];
Sum += a[I+3];
}
Sum += sum1+sum2+sum3;
这个是不是应该是代码2效率高啊~?

代码一
Vector vi;
Int len = vi.size();
For(int I = 0; I < len; i++)

代码二
vector vi;

for(int i = 0; I < vi.size(); i++)
….
为什么多了一行int len =vi.size();
这样速度就要快些呢?

这个有关缓存的利用
我原来的代码中方法一对缓存的利用比较好,呵呵

[ 本帖最后由 yyfn风辰 于 2010-5-27 17:03 编辑 ]

这个就是不用每次都求长度,所以要快,但是有些编译器可能会自己优化。

这样的程序用SSE指令会更有效

不懂sse,对了,难不?

哦~~学习了~