使用intel vtune寻找热点程序 优化代码
还是上次那个苦逼的web搜索作业,本来做完了就算了吧。手贱的我有把程序塞给vtune跑了跑,发现了几个热点程序之后,我就不淡定了。好像总是有点淡淡的忧怨在心头无法释怀。于是就走上了优化这条不归路。其实这条不归路是很容易走完的。尤其是像我这样的菜鸟,把所有优化的招的用完了,这条路就走到了尽头。我就不晒我的文采了,直接讲重点吧。
背景知识:我的主要工作是构建一个文档向量。关于这个文档向量的定义可看我的上一篇博客。我用的最多的是统计一个单词出现了多少词,还有就是一个单词出现在几个文档中。我采用了std::map<string,int>这种数据结构,可以方便我找到一个单词出现了多少词。我的优化就是从坑爹的std::map使用开始的。。。
我使用的vtune版本是VTune Amplifier XE 2011.编程环境是fedora 15.选择的类型是hot function。点击start按钮,VTune就开始分析热点程序了。第一次运行的时候需要统计的文档比较少。运行时间是0.24s,得到的第一个热点函数是AccFreqIdf。这个函数的功能是统计一个单词出现了几次,和一个单词出现在几个文档中。 双击函数名按钮可以打开相应的源文件。发现一下代码的第一行是个瓶颈:
1 if( !m_IDF[keyword].showup ) //hot function detected by vtune
2 {
3 m_IDF[keyword].num++;
4 m_IDF[keyword].showup = true;
5 }
对于一个关键词出现在几个文档中这个问题我一开始想了这么一个法子:定义一个结构体包括int num和bool showup;初始状况下,showup为false。在一个文档中一个关键字只要出现了,他的showup就是true。从此以后无论出现几个该关键字num都不会增加了。if语句可以保证在一个文档中某关键字只要出现了,计数就增加1.而不管他出现了几次。但是这个判断语句使用的太频繁了,成为了一个hotfunction。于是就有了下面的代码:
1 map<string,int> tmpIDF;
2 tmpIDF.clear();
3
4 while(!file.eof())
5 {
6 //read in every words
7 file >> keyword;
8 //tmpIDF.clear();
9 tmpIDF[keyword] = 1;//set keyword to 1
10 //----------------------------
11 //acc idf
12 //if( !m_IDF[keyword].showup ) //hot function detected by vtune
13 //{
14 // m_IDF[keyword].num++;
15 //m_IDF[keyword].showup = true;
16 //}
17 timePerWord[keyword]++;
18
19 }//end while
20 //insert showup keywords to m_IDF map
21 map<string,int>::iterator iterTmpIdf = tmpIDF.begin();
22 for( ; iterTmpIdf != tmpIDF.end(); iterTmpIdf++)
23 {
24 //cout << iterTmpIdf->first << endl;
25 m_IDF[iterTmpIdf->first].num++;
26 }
27 }
在每一个文件内我定义了一个std::map<string,int> tmpIDF。如第9行所示,在这个文档内出现的所有关键字都插入到tmpIDF中。tmpIDF只存储该文件出现了哪些关键字而不去管每个关键字出现了几次。统计完当前文件之后,将tmpIDF合并到m_IDF这个大map中。如第21至26行所示。我使用m_IDF这个map来存储所有文档的每个关键字出现在几个文档中。这样可以避免if语句的使用。再送入vtune分析,发现效率得到了明显的改观。运行时间从0.24s降低到了0.12s。
你可能会说这个运行时间太短了,如果文档数量增加会怎么样呢?当我增加文档的数量之后发现程序运行时间是2.4s 有了一个新的瓶颈。热点函数变成了CalcTFIDF这个函数中的如下代码的第4行:
1 for(map<string,NumIDF>::iterator idfIt = m_IDF.begin(); idfIt!=m_IDF.end(); ++idfIt)
2 {
3 //for every keywords
4 int freqij = elem[idfIt->first];
5 int maxj = iter->second;
6 int N = m_MaxFreq.size();
7 float ni = (float)idfIt->second.num;
8 float tmp = (freqij/(float)maxj)*log((float)N/ni);
9 rkdv.push_back(tmp);
10 }
这段代码的功能是构建文档向量。举个例子来说明文档向量吧:
现在一个三个文档a.txt b.txt 和c.txt。a.txt中的关键字有happy。b.txt中的关键字有merry. c.txt中的关键字有new year。所有关键字各出现一次。所有可以构建如下的向量:
happy merry new year
a.txt 1 0 0 0
b.txt 0 1 0 0
c.txt 0 0 1 1
于是得到的文档向量分别是:a.txt(1,0,0,0) b.txt(0,1,0,0) c.txt(0,0,1,1)。当然这只是个例子,真正的计算还要复杂一些如上面代码第8行。
在老方法中,我把每个文档出现的关键字都存在elem这个变量中。m_IDF中存储的是所有文档所有的关键字。我把所有文档中所有的关键字都用elem[idfIt->first]来查询一下。比如说。a.txt的map是这样存的:(happy,1)(happy 出现一次)。但是执行完elem["merry"];elem["new"];elem["year"]之后,虽然可以得到正确的结果,但是operator []是有插入功能的,于是现在a.txt存的是这样的一个map:(happy,1)(merry ,0)(new ,0)(year,0).有很多不必要的操作 。所以这个函数非常耗时间。于是我采用了如下的改进方法:
1 map<string,NumIDF>::iterator iter = m_IDF.begin();
2 int index = 0;
3 for(; iter != m_IDF.end(); iter++)
4 {
5 iter->second.showup = index++;
6 }
7 .....
8 ......
9
10 for( ; elemIter != elem.end(); elemIter++)
11 {
12 //get index in vector
13 const NumIDF& nidf = m_IDF[elemIter->first];
14 int indVec = nidf.showup;
15 //get the pointer to write to
16 advance(dvIter,indVec - lastInd);
17 lastInd = indVec;
18 int freqij = elemIter->second;
19 int maxj = iter->second;
20 int N = m_MaxFreq.size();
21 float ni = (float)nidf.num;
22 float tmp = (freqij/(float)maxj)*log((float)N/ni);
23 *dvIter = tmp;
24 }
文档向量我是用vector<float>来存储的。但是这个向量有很多值是0.因为在该文档中不会出现所有的关键字。首先我遍历了一次m_IDF这个map把每个关键字出现在几个文档中和该关键字在m_IDF是第几个存在了一个教NumIDF的结构体中。对于每个文档我只遍历该文档中出现的关键字。查找该关键字在m_IDF中是第几个。于是就把vector<float>向量的第几个元素设置成非0的值。至于vector<float>在其他位置上的值设置为0.这样之后程序的运行时间从2.5s减少到了0.5s。高兴了我一个上午,而且我打算交作业了,不优化了。。。

浙公网安备 33010602011771号