1. 故事
背景:线上流式计算,某个关键模块Mario一个大业务版本(带来输入数据double)升级上线
注:流式计算的典型范式之一是不确定数据速率的事件流流入系统,系统处理能力必须与事件流量匹配。
故事分为3个阶段
1)上线后,线上报警,Mario出现数据积压(处理能力无法满足当前线上流量)。
经查:Mario中经过处理后的数据需要进入远程数据库,处理线程以同步的方式将数据插入远程数据库,这种方式,使得线程处理能力急剧下降。
解决:数据写入磁盘,另外一个程序入库
2)第一个问题解决后,再次出现性能问题
解决:使用Tcmalloc(参考:http://blog.csdn.net/yfkiss/article/details/6902269)
3)使用Tcmalloc之后,发现线上CPU抖动非常厉害,并且有一定概率程序hang住
经查:一个求去重后的数据个数的算法,采用字典进行计算,频繁的对字典进行构建和删除,使得系统频繁申请、释放内存,从而导致cpu抖动。
解决:对于小数据,采用O(n^2)的算法,对于大数据,采取O(n)的算法(http://blog.csdn.net/yfkiss/article/details/6754786)。
2. 原理
程序性能优化可以做三个层次的事情。
1)设计
2)算法&数据结构
3)代码
当然,以上三个层面只是一般程序员可以做的优化,之上还有架构,之下还有运行系统和硬件。
设计:个人理解是最重要的一块,包括:数据如何处理?多线程还是单线程?多线程之间如何同步?锁粒度多大?是否使用内存池?同步还是异步等等
算法和数据结构:对算法优化往往可以使得程序性能有数量级的飞跃。
代码调优:运行中的程序有一种典型情况:20%的代码占了80%的运行时间,优化的重点是这20%的代码。
回到story,第一个阶段的问题,很明显是设计出现问题,在出现需要网络交互的时候的,考虑异步方案。
第二个阶段使用了tcmalloc,本质上是从设计、算法、代码多个角度对内存分配做了优化,只是这个优化是别人帮你做的~
第三个阶段属于算法优化,原有算法非常快,但带来了内存操作的过大开销,我们的应用中,数据集99%都非常小(数据集平均大小为2),因此,对于小数据集,采用O(n^2)的算法,对于大数据集,采用O(n)的算法,实际证明非常有效。所以,没有最好的算法,只有最适合的算法。
3. 如何找出热点代码
1)梳理程序,找出执行热点。很土,但是很有效
2)辅助工具:Google Cpu Profiler
方法1更多的是依靠经验,辅助工具Google Cpu Profiler简要介绍下。
Google Cpu Profiler是 google-perftools的一部分(google-perftools还包括Tcmalloc、Heap checkedr、Heap profiler)
其使用非常简单:
链接 profiler库及设置环境变量CPUPROFILE
4.使用Google Cpu Profiler进行性能分析的一个实例(使用 LD_PRELOAD,懒人法,不需要重编译)
code:
#include <iostream> #include <time.h> using namespace std; const int MAX_OPERATION = 2; enum TYPE{MINUS = 0, PLUS}; int random(unsigned int n) { if(n != 0) { return rand() % n; } else { return 0; } } void make_expression(int n) { int left = random(n); int operation = random(MAX_OPERATION); int right = (PLUS==operation ? random(left) : random(n)); cout << left << (operation==PLUS ? "-" : "+") << right << "="; } void make(int n, int max) { for(int i = 1; i <= n; i++) { make_expression(max); if(0 != i % 3) { cout << "\t" << "\t"; } else { cout << endl; } } } int main(int argc, char** argv) { srand((int)time(0)); if(argc != 3) { cout << "we need 3 argc" << endl; return 1; } make(atoi(argv[1]), atoi(argv[2])); cout << endl; return 0; }设置环境变量 LD_PRELOAD和CPUPROFILE
分析:
1)文本分析:
$ ./pprof -text ./test ./myprofiler
Using local file ./test.
Using local file ./myprofiler.
Removing killpg from all stack traces.
Total: 508 samples
149 29.3% 29.3% 149 29.3% __write_nocancel
47 9.3% 38.6% 47 9.3% fwrite
41 8.1% 46.7% 41 8.1% _IO_file_xsputn@@GLIBC_2.2.5
41 8.1% 54.7% 41 8.1% random
33 6.5% 61.2% 33 6.5% std::operator<<
32 6.3% 67.5% 32 6.3% std::basic_ostream::operator<<
29 5.7% 73.2% 29 5.7% std::has_facet
26 5.1% 78.3% 26 5.1% std::num_put::_M_insert_int
15 3.0% 81.3% 15 3.0% std::basic_ostream::sentry::sentry
14 2.8% 84.1% 97 19.1% make_expression
13 2.6% 86.6% 73 14.4% std::num_put::do_put
11 2.2% 88.8% 11 2.2% random_r
9 1.8% 90.6% 9 1.8% strlen
7 1.4% 91.9% 7 1.4% CXXABI_1.3
7 1.4% 93.3% 7 1.4% std::basic_ostream::put
6 1.2% 94.5% 135 26.6% make
4 0.8% 95.3% 4 0.8% _IO_do_write@@GLIBC_2.2.5
4 0.8% 96.1% 4 0.8% _init
4 0.8% 96.9% 4 0.8% std::time_put::put
3 0.6% 97.4% 3 0.6% _IO_file_write@@GLIBC_2.2.5
3 0.6% 98.0% 3 0.6% fflush
3 0.6% 98.6% 3 0.6% std::__numpunct_cache::_M_cache
2 0.4% 99.0% 2 0.4% __gnu_cxx::stdio_sync_filebuf::file
2 0.4% 99.4% 2 0.4% std::basic_ios::widen
2 0.4% 99.8% 2 0.4% std::endl
1 0.2% 100.0% 1 0.2% rand
0 0.0% 100.0% 1 0.2% _DYNAMIC
0 0.0% 100.0% 8 1.6% __bss_start
0 0.0% 100.0% 143 28.1% __libc_start_main
0 0.0% 100.0% 143 28.1% main
2)图形分析
$ ./pprof -dot ./test ./myprofiler > test.dot
可是使用Graphviz打开dot文件