您的位置:首页 > 编程语言 > C语言/C++

浅谈C\C++代码优化中的一些小技巧

2014-05-12 09:14 561 查看


转自:/article/9498241.html


编写C\C++程序快两年,对于代码优化有很深的感触。个人认为,代码优化是一门很深的学问,而作为码农,我们总是在实践中不断更新自己对它的认识和理解。对于这个很大很深的问题,我只是根据自己的所学所见以及所悟来谈谈代码优化中的效率问题,希望对初学者有一些帮助,如有不对的地方还望指正。


一、定义

所谓代码优化是指在不改变程序运行结果的前提下,使最终生成的目标代码的运行时间更短,时空效率得到优化。这里,我结合自己的图像处理方面的一些经历谈谈如何如何减少运行时间、提高运行效率。个人认为主要包括硬件加速和软件加速两个方面。


二、硬件加速

顾名思义,硬件加速指的是充分利用计算机现有的的硬件资源编写代码,进而提高代码的运行效率。据我所知,常见的适合计算机的硬件加速方法有:GPU,OpenMP以及SSE。


GPU

GPU(Graphic Processing Unit)是图像处理器,它是显卡的大脑,早期主要用于电脑的2D\3D图形计算。随着科技的发展,GPU开始在并行计算等方面得到广泛的应用,其优势在于它包含很多小的独立处理单元,这些处理单元可以并行运算。这一特点决定了GPU相对于CPU而言在并行数据的处理上具有更大的优势,如图像处理中的计算梯度、反转灰度等运算。

关于GPU编程,首先需要硬件上的支持,即计算机需要有独立显卡,其次需要软件上的支持,即编程环境以及相应的GPU接口,如VS2008和NVIDIA的CUDA。CUDA是NVIDIA推出的一个GPU运算平台,我们可以理解API。一般而言,如果我们的电脑有独立显卡,在安装显卡驱动、CUDA开发包以及编程环境(如VS2008)之后,我们便可以在相应的工程项目中嵌入.cu格式的文件用于GPU编程。有关GPU编程的详细过程这里就不详细叙述了,感兴趣的同学可以参考CUDA提供的用户手册以及《GPU高性能编程CUDA实战》一书,前者比较详细,后者比较易懂。


OpenMP

百度百科的定义:OpenMP是由OpenMP Architecture Review Board牵头提出的,并已被广泛接受的,用于共享内存并行系统的多线程程序设计的一套指导性的编译处理方案(Compiler Directive),支持语言包括C语言、C++和Fortran等,适合并行处理的数据。

在VS2008下面使用OpenMP比较简单,我们需要将项目》配置属性》c/C++》语言下的OpenMP支持打开,并在工程项目中包含omp.h头文件,这样所建的项目便支持OpenMP了。


SSE

SSE(Streaming SIMD Extensions,单指令多数据流扩展)指令集是Intel在Pentium III处理器中率先推出的。由SSE部分和MMX部分组成。SSE部分负责处理浮点数,MMX 部分专门计算整数。SSE部分可以同时处理四个浮点数。VS2008下需要包含emmintrin.h头文件。举个例子,如需要计算向量中每个元素相乘的结果,其运算速度是原始方法的4倍。需要注意的是,SSE需要数组的首地址是16的整数倍。

原始方法
for(int i = 0; i < nSize; i++)
{
*pResult = (*pArray1)*(*pArray2);
pArray1++; pArray2++; pResult++;
}


SSE方法
__m128 *p1 = (__m128*)pArray1;
__m128 *p2 = (__m128*)pArray2;
__m128 *p3 = (__m128*)pResult;
for(int i = 0; i < nSize; i++)
{
*p3 = _mm_mul_ps(p1,p2);
p1++; p2++; p3++;
}



三、软件加速

个人认为,所谓的软件加速是值用最小的计算代价达到所需的目标,一般而言,软件加速需要对语言本身比较熟悉,同时需要了解相关问题的快速算法,这样才能够写出高效率的算法。这里,我结合自己写代码过程的体会从三个方面介绍一下软件加速的几种方法,包括变量、函数等方面的精简,去除冗余代码以及使用快速算法等。


变量、函数等方面的精简

用尽量小的数据类型

能用bool型变量不用char型;能用char型变量不用int型,虽然有时不同数据类型的运算速度可能相同;如,在表示图像数据时,由于图像数据一般为0-255之间,对其进行的大部分运算也都在0-255之间,这时我们只需使用uchar型即可。

减少运算强度

尽量使用移位、取余运算代替乘法、除法等; 如b=a/4可替代为b=a>>2,b=a%4可替代为b=a&3,b=3*a可替代为b=a<<1+a。

避免不必要的整数除法; 如avg=sum/w/h可替代为avg=sum/(w*h)。

使用增量和减量操作符; 如a=a+1可替代为a++。

循环优化

拆解小循环,即不要将一些可列举的情况写成循环,特别是该小循环嵌套在大循环中情况。

switch语句中概率较大的情况放在靠前的位置。

使用内联函数


去除冗余代码

所谓的去除冗余代码是指程序在不知不觉中进行了很多重复的运算,而这些重复运算其实可以通过巧妙的安排进行避免。这里说两个图像处理中常见的方法:查找表和积分图。

查找表

所谓的查找表比较通俗的说法就是已经赋好值的数组。当我们在程序中需要用到查找表中的某种情况时,只需要调用相应的数组元素即可,如计算图像的梯度。正常情况下,我们计算完一个像素点水平方向和垂直方向的差值dx和dy之后,通过公式sqrt(dx*dx+dy*dy)和atan2(dy,dx)便可分别得到梯度的幅值和方向。但是,这种方法存在很大的冗余,它需要频繁地调用开方函数和反正切函数,而这个代价是很大的。仔细想想,由于像素值属于[0,255],那么dx和dy便属于[-255,255],其情况是可列举的。如果我们把所有(dx,dy)对应的梯度的幅值和方向提前计算好,并放到一个数组构成查找表,那么当给定一个像素点的dx和dy,我们只需要索引到其对应的数组元素即可。相对于原始方法而言,这样能够大大地减少运算量,提高代码效率。

积分图

积分图指计算图像中每个像素点对应的左上角像素值之和。最直观的方法可能是依次遍历每个像素点,然后计算该像素点左上角的像素值之和。这种方法虽然简单、直观,但是计算复杂,没有利用当前已有像素点和的信息。这里,给一种简单,有效的方法,它充分利用了当前已有像素点和的信息。
for(i = 0; i < w; i++)
{
offset = i;
pResult[offset] = (int)data[offset];
for(j = 1; j < h; j++)
{
offset += w;
pResult[offset] = pResult[offset-w]+(int)data[offset];
}
}
for(j = 0; j < h; j++)
{
offset = j*w;
pResult[offset] = (int)data[offset];
for(i = 1; i < w; i++)
{
offset++;
pResult[offset] += (int)data[offset];
}
}


可见,积分图的计算是有技巧的。那么积分图有什么用了,其实积分图的出现本身也是为了加快一些问题的运算速度,如大量计算图像中局部区域的像素和。


使用已有的快速算法

许多问题(例如查找、排序以及傅里叶变换等)其实在学术界都有相应的快速算法,我们所需要做的其实就是搜索,常见问题的快速算法可能baidu一下就能搞定,对于一些特定的问题,我们可能需要google出相应的论文,并根据论文思想编写相应的快速算法。下面简要地列举查找、排序以及傅里叶变换等问题的快速算法。

查找

包括二分查找、分块查找以及哈希查找等。

排序

包括快速排序、堆排序以及归并排序等。

傅里叶变换

可以用fft实现,目前比较最快的fft算法开发包应该是fftw,matlab应该用的就是这个开发包。


总结

个人感觉硬件加速相对而言简单,对于个人能力要求相对于较低,但是可能需要一定的硬件成本,而软件加速需要时间和知识的积累,对于个人能力要求较高,但是不需要烧钱。至于效果,硬件加速和软件加速属于两种不同方法,我们需要根据实际情况判断那种方法更适合,例如,很多时候我们花费了很大的精力进行代码优化后的速度还比不上简单GPU加速后的速度。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: