手中的项目逼近后期,大多做一些性能调优的事情,最近发现了一个滥用vector带来的性能问题,跟大家分享一下。
问题的场景是这样的:
在一组循环的内部(循环次数很多),有一个计算模块,用以计算每次循环条件下某个指标的值。郁闷的是,这组循环计算的性能老是不达标,循环执行需要10s左右的时间,通过调试逐步定位瓶颈,发现下面的代码尽然消耗了2s多的时间,占总计算时间的百分之20多!
vector<double> tmp(2, 0);
调试过程中统计过,一行长长的复杂数学运算(比如sqrt(a * b / c + d^2 + sin(e) + atan(b)) ),循环执行同样次数,耗时都不到1s,上面这行仅仅是定义一个vector竟有如此大的威力。
STL在带给我们方便的同时,也隐藏了大量实现细节,vector的内部实现是一个动态数组,其数据缓冲区是在运行时是从堆上new出来的。
堆申请空间的效率比栈要低的多,具体原因主要在于:栈上的内存地址分配在编译时就已经完成了,执行的时候只是用编译时确定的地址直接存取即可(可执行文件一运行,进程空间中就有固定的若干M的栈空间)。而堆上内存分配在运行时完成的,且有一套复杂的算法,比如C++的new会先在该进程目前的堆中搜索足够大的可用空间,如果搜索不到,还要通过系统调用向操作系统申请更大的堆空间,这样一来二去的,如果仅仅为了2个double的简单存取(即不考虑数组长度扩展),实在是不划算。
将vector<double> tmp(2, 0);修改为double tmp[2]; tmp[0] = 0; tmp[1] = 0;瓶颈消失。
这个问题其实比较简单,不过给了我一个实际的机会,体验到堆的效率比栈慢的多,之前都是看书上这么写的,没有直接的感官刺激,相信今后在写代码的时候,这种感觉会成为设计直觉的一部分,不知不觉中发挥重大作用。
这里也体现了敏捷开发中的一个重要思想,书写刚刚好的代码,这“刚刚好”三个字,也在于能用简单数据结构搞定的事情,就不要急着用复杂的数据结构搞定。