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,懒人法,不需要重编译)
#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
export "LD_PRELOAD=/home/work/zhouxm/google-perf_1.8.3/lib/libprofiler.so"
export "CPUPROFILE=/home/work/zhouxm/google-perf_1.8.3/bin/myprofiler"
注:LD_PRELOAD指定在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。这个环境变量相当危险,慎用
CPUPROFILE指定profiler文件保存位置及文件名
运行:
$./test 10000000 10000 1>/dev/null
PROFILE: interrupts/evictions/bytes =508/228/12704
分析:
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文件
|