|
关于性能优化,有个观点是“不要优化”。这是因为软件架构未建好的情况下,优化的成本很容易超出收益。但是这不代表性能不重要。如果性能不重要,那就不要用C++编程,改用其它的编程语言,可以提高开发效率。我们现在还在用C++,唯一的原因是性能。下面写几条我认为的C++高性能编程的经验。
1)优化需要建立在实测的基础上,同时要考虑Amdahl定律。即使是经验丰富的程序员,也很难通过直觉判断性能瓶颈到底在哪里,直觉通常是错的(这一点也适用于管理学,管理优化也需要建立实测基础上,管理者通过直觉拍脑袋的决策也多半是错的)。比如说,有的程序初看觉得性能瓶颈是数值计算,其实性能瓶颈在于I/O接口的等待。还有必须考虑Amdahl定律,比如说:一个子程序占程序总时间的50%,再怎么优化这个子程序,再怎么投入智力资源,整个程序的性能最多可以提高到2倍。(这一点同样也适用于管理学,很多看起来非常有道理的管理变革没有预期效果,无非是收益-成本的比较问题)。
2)控制好内存布局,充分利用局部性原理,写出“缓存友好型”程序。具体的计算机体系结构的原理就不展开讲了(可参考我之前的博客《矩阵乘法的优化》)。传统的教程更关注CPU而不是存储,我个人认为存储对性能的影响要大多了。
下面举几个例子:
怎么样表示稠密矩阵(即二维数组),有不同的方式。《常用算法程序集(C++语言描述)》(第四版)采用下面的方式:
Double **a;
a = new double *[m];
for(i = 0 ; i < m ;++i) {
a[i] = new double[n];
}
这种方法就不妥,因为矩阵在计算机内存的存储不能保证连续性。
还有的人用嵌套vector来实现:
vector<vector<double> > a_mat;
这种方法同样不妥,原因一样。
正确的做法是,不管矩阵表象是什么样子,最终的存储都在一维的连续内存上。C++之父在好几本书上都有矩阵的例子,他的实现就是这样。
3)正确使用STL
怎样正确使用STL,已经有很多教程了。比如说,优先使用vector(因为vector的内存连续性)。在使用vector前预先分配内存(用reserve命令)。排序数查找,用排序vector有可能比map更快(还是因为内存连续性)。
下面重点谈谈嵌入式编程能否使用STL。有人一刀切的认为嵌入式编程不应该使用STL,这样相当于把C++的精华都丢掉了,我非常不赞成。但嵌入式编程确实有其特殊性。如果把嵌入式程序分为两部分:初始化和运行。那么,初始化部分显然是可以用STL,这从软件工程的角度可以减少许多开发量。运行部分需要避免内存销毁和重分配,从而保证实时性。STL在这阶段确实应避免new和delete的出现,这其实是可以实现的(前提是对STL的原理要熟悉)。还可以考虑把STL管理的内存转交给低级指针,我就经常这样写:
double *a_ptr =&A_vec[0];
上面的代码表示的是把初始化阶段由vector<double>管理的内存(即A_vec)首地址传递给原始指针,然后在运行阶段由原始指针进行低层次的计算和操作。
4)用空间换性能
用更多的内存可以换取性能。比如求三角函数不一定需要级数展开,也可以预先制定一个三角函数表存储下来,然后查表,再插值。速度可能比级数展开还快一些。再比如,有一个很复杂的系统,用户办理业务需要查询一些东西。用户不可能像随机数那样随机的查,总有些记录更容易重复查到。可以把容易查的记录缓存到内存中,下次再查就不用到数据库里面去查了。
5)注意C++的特有规范
C++程序有一些特有的东西,比如说避免无谓的内存复制(往函数传递参数,只要不是内置类型,就要用引用或指针)。在编译期可以确定的数字就不要在运行期再算(使用constexpr)。控制临时对象的影响(使用右值引用)。
这些特有的东西往往在别的编程语言中不存在或者很难理解,需要专门去学习。
Archiver|手机版|科学网 ( 京ICP备07017567号-12 )
GMT+8, 2024-12-14 05:03
Powered by ScienceNet.cn
Copyright © 2007- 中国科学报社