这几天,一直忙于测试程序性能,基于rational 的quantify 工具进行性能测试,能够对exe,dll文件测试,还是很不错的,可以在ibm官方网站下载,不算太大。因为trial版,没有把数据保存起来。
以前用过parasoft的测试工具,不记得了,当时觉得那个工具挺好的。
quantify可以选择line,function,code,一般选择line就好了。
这类测试工具,是基于细粒度的测试情况,不能较直观或者有效的分析逻辑处理部分,可以认为是微观分析;我们也需要宏观分析程序代码块,那部分代码块执行次数,耗时等等。
写了一个windows 下的简单的测试程序代码块,用于从逻辑换上来分析程序性能的源码;如果需要支持Linux,修改计时函数就行了。
st_stat.h
#ifndef STAT_H #define STAT_H #include<map> #include<string> using namespace std; /*** wx,取消原有用宏方式,但测试函数的开销可能会增加测试的精度 **/ struct st_stat{ public: st_stat():call_count(0),time(0){} LARGE_INTEGER start; LARGE_INTEGER end; int call_count; __int64 time; }; /**测试函数性能或者代码块的 */ class Runnable { public: explicit Runnable(const std::string& name): _name(name) {} ~Runnable() {} const std::string& name() const { return _name; } inline void begin() ; inline void end() ; void diff_time(LONGLONG diff) { stat.time -= (stat.end.QuadPart-diff); } int getCallCount(){return stat.call_count;} int getCallTime(){return stat.time;} st_stat stat; private: std::string _name; }; class test_suite { public: static test_suite& get_instance() { static test_suite me; return me; } void setWorkingDir(const std::string& val) { _working_dir = val; } const std::string& getWorkingDir() const { return _working_dir; } void setTestFilter(const std::string& val) { _test_filter = val; } const std::string& getTestFilter() const { return _test_filter; } void addTest(Runnable* test) { test_vec.insert(pair<string,Runnable*>(test->name(),test)); } void func_begin(const std::string &name); void func_end(const std::string &name); void init(const std::string &dir); void finish(); private: test_suite():fp(NULL),_working_dir(),_test_filter(){} void printHeading(); void printFooter(); void printError(const std::string&msg); std::string _working_dir; std::string _test_filter; typedef std::map<std::string,Runnable*> MAP; typedef MAP::iterator IT; typedef MAP::const_iterator const_it; MAP test_vec;//所有需要测试的函数列表 std::vector<Runnable*> none_init_vec;//没有初始化的,需要new对象,因此最后需要释放 FILE *fp; }; void call_stat_begin(st_stat*pstat); void call_stat_end(st_stat*pstat); #if defined ST_TEST_PERFORMANCE #define STAT_TEST_INIT(name) \ struct st_##name##_test: Runnable { \ st_##name##_test(): Runnable(#name) { \ test_suite::get_instance().addTest(this); \ } \ } name##_test_instance #define STAT_TEST_BEGIN(name) \ test_suite::get_instance().func_begin(#name) #define STAT_TEST_END(name) \ test_suite::get_instance().func_end(#name) #define STAT_GLOBAL_INIT(fileDir) test_suite::get_instance().init(fileDir); #define STAT_GLOBAL_FINISH() test_suite::get_instance().finish(); #else #define STAT_TEST_INIT(name) \ struct st_##name##_test: Runnable { \ st_##name##_test(): Runnable(#name) { \ test_suite::get_instance().addTest(this); \ } \ } name##_test_instance #define STAT_TEST_BEGIN(name) (void*)(0) #define STAT_TEST_END(name) (void*)(0) #define STAT_GLOBAL_INIT(fileDir) #define STAT_GLOBAL_FINISH() (void*)(0) #endif #endif
st_stat.cpp
#include"st_stat.h" #ifdef _WIN32 #include<windows.h> void call_stat_begin(st_stat*pstat) { pstat->call_count++; LARGE_INTEGER large_integer; BOOL bRet = ::QueryPerformanceCounter(&large_integer); _ASSERT(bRet); pstat->start = large_integer; } void call_stat_end(st_stat*pstat) { LARGE_INTEGER large_integer; BOOL bRet = ::QueryPerformanceCounter(&large_integer); _ASSERT(bRet); pstat->end = large_integer; pstat->time += pstat->end.QuadPart-pstat->start.QuadPart; } void Runnable::begin() { ::call_stat_begin(&stat); } void Runnable::end() { call_stat_end(&stat); } void test_suite::func_begin(const std::string & name) { // Run test initializers const_it it = test_vec.find(name); if(it!=test_vec.end()) { LARGE_INTEGER large_integer; BOOL bRet = ::QueryPerformanceCounter(&large_integer); _ASSERT(bRet); (*it).second->begin(); return; } Runnable *pTest = new Runnable(name); test_vec.insert(pair<string,Runnable*>(name,pTest)); none_init_vec.push_back(pTest); pTest->begin(); } void test_suite::func_end(const std::string & name) { // Run test initializers const_it it = test_vec.find(name); if(it!=test_vec.end()) { LARGE_INTEGER large_integer; BOOL bRet = ::QueryPerformanceCounter(&large_integer); _ASSERT(bRet); (*it).second->end(); (*it).second->diff_time(large_integer.QuadPart); } //printError(name); /*vector<Runnable*>::iterator it = test_vec.begin(); for (; it != test_vec.end(); ++it) { if ((*it)->name()==name) { LARGE_INTEGER large_integer; BOOL bRet = ::QueryPerformanceCounter(&large_integer); _ASSERT(bRet); (*it)->end(); (*it)->diff_time(large_integer.QuadPart); } }*/ } void test_suite::printHeading() { if(fp) { fprintf(fp,"函数名,调用次数,调用时间PerformanceCounter\n"); fprintf(fp,"start Tick Count=%d ms\r ",::GetTickCount()); } } void test_suite::printFooter() { if(fp) { fprintf(fp,"end Tick Count=%d ms\r",::GetTickCount()); } } void test_suite:: init(const std::string &dir){ setWorkingDir(dir); if(fp) { fclose(fp); fp = NULL; } if(getWorkingDir().size()>0) { std::string filePath(dir); filePath.append("test_performance.csv"); fp=fopen(filePath.c_str(),"w+"); } else { fp=fopen("test_performance.csv","w+"); } printHeading(); } void test_suite::finish() { if(fp) { for(IT it = test_vec.begin();it!=test_vec.end();it++) { fprintf(fp,"%s,%d,%I64d\r",it->second->name().c_str(),it->second->getCallCount(),it->second->getCallTime()); } } if(fp) { fclose(fp); fp =NULL; } std::vector<Runnable*>::iterator it = none_init_vec.begin(); for(;it !=none_init_vec.end();it++) { if(*it!=NULL) { delete *it; *it=NULL; } } } void test_suite::printError(const std::string&msg) { if(fp) { fprintf(fp,"%s",msg.c_str()); } } #endif
使用方式,也很简单
开始时:
STAT_GLOBAL_INIT("文件路径");执行一次
结束时:
STAT_GLOBAL_FINISH()
这两个宏,特别是最后一个宏,可以优化后,省略这个调用,为了保持与INIT,所以加上了。
打算再优化下,INIT和FINISH默认都可以省略,这样就更简洁明了
然后就是如下调用了;
STAT_TEST_BEGIN("测试名字");
XXXXXX;测试代码
STAT_TEST_END("测试名字");
经过初步Quantify测试结果,得到我现有的程序如下问题:
(1)std:string构造和销毁,耗时太多
(2)malloc和delete调用次数太多,耗时很大
因此,初步从微观可以看出,主要是频繁字符串操作,还有小对象的分配回收;
基本上可以确定方案:需要优化处理字符串,需要使用内存池管理小对象。
而基于程序逻辑测试结果:
函数名 | 调用次数 | 调用时间PerformanceCounter |
start Tick Count=16480171 ms | ||
AddLineGraphToMemory | 2043768 | 842106440 |
AddRegionGraphToMemory | 317901 | 1462981839 |
GetShapeFeatureValue | 8739066 | 0 |
LINE_裁剪 | 2043768 | 359172490 |
OutPutReulstFile | 70 | 347857107 |
REGION_裁剪 | 274746 | -985524651 |
ReadEveryShapeFile | 3702 | 1332493360 |
ReadEveryTabFile | 249 | 1898886781 |
上面逻辑宏观测试结果,REGION_裁剪和AddRegionGraphToMemory的耗时太多,这部分逻辑需要额外关照,嘿嘿!
(1)丰富下这个测试代码,让他支持单元测试,即可测试某一个函数
(2)修改bug,当时间太长,出现负数了