结构优化:
在经过了10-15h左右的编码后,我终于完成了第一版的代码的编写,所有功能大致齐全,但是字符和单词数离助教的答案有大概一万个的差距,调了半天也不想调了,根本就是玄学。
第一版的代码主要含有一个字符——单词转换器,以及单词词组处理器组成。在这个结构里面,转换器可以说是我写的比较开心也比较自豪的一个工具,但是目测优化也需要从这个地方来进行,因为每次都去调用类方法,会在调用上存在开销,当然了 优化需要结合后面的性能分析来进行。
而单词词组处理器,就可以说是我这个宇宙最慢第一版的罪魁祸首了。我用了一个占用大量空间(因此建立时间开销很大),同时查找效率也不如想象中好的结构,基于字典序的二叉排序树,由于一开始图方便,我没有对其作平衡的优化,这也是另一个可以优化的方向,但是在全部功能实现后,我都不敢在debug环境下测试,因为可能10分组往上走是肯定的。在Release环境下的速度也接近4分钟,跟同学们的30-40s相比,这可以说是龟速了。因此,不用看热图就可以知道,这个程序的瓶颈是出现在最初的设计上,而非具体的代码细节上,对这样的代码,我们有必要在第一步做大的改动。而事实证明,在我选用了unordered_map之后,我的平均速度瞬间来到了30-40s左右,性能直接提升了8倍,这当然是因为unordered_map库很强大,为我们提供了一个查找时间几乎为O(1)的结构,也是因为其建立的开销多花在空间上(还没有我的二叉树空间大orz),而非时间上,因此这样的优化可以说是必须而且非常大的。
性能分析:
经过了大的结构调整后,接下来就要开始动用热图进行分析了,首先上一张热图:
这其中,占比较大的地方为
1、word_classifier::judge() //判别器方法 13.2%,
2、phrasemap和wordmap的查找(如果查找不到的话)占比总和为21.2% 其中phrasemap为14.2% wordmap为7.0% 但显然这是由于phrase的key值更大,所占据的哈希运算时间更长所导致的。
3、infile.get(c);//读取字符,可以说文件流的操作是最占时间的了,占35.2%;
4、word_to_word()&phrase_to_phrase()可以理解为“单词流”、“词组流”的操作,占比不高,总和约为5%左右;
5、对string的内存操作,零零碎碎,但是总和起来接近10%,有必要进行优化;
由于时间关系,在提交代码的时候,我并没有来得及对以上五个方向都进行进一步的优化,具体所做的优化请见LInux性能分析一节。
Linux 性能分析
使用gprof进行性能分析
命令行参数:
g++ stat.cpp -g -pg
./a.out Test/
gprof -p
具体分析结果见下文(为节省篇幅删去部分函数调用及时间记录)。
具体情况分析与热行图类似:由于infile.get(c)这一个针对IO的操作被分散在各个零碎的函数记录中,所以从调用数来观察,可以看到,部分函数日志被调用超过一亿四千万次,这些代码显然是针对字符数和读取字符的操作,应该作为主要的优化方向。
结合计算机体系结构的知识可以知道,对于IO的操作往往会成为性能瓶颈,再加上在本次项目实现过程中,IO操作采取类似“忙等待”的方式,且每次读入一个字符,这样显然会严重降低执行效率,因此我们可以考虑采取异步多线程来读取文件中字符的方式进行优化。
与此同时,可以看到针对hash表的遍历查找操作耗费了大量的时间,但是这些时间是必要的,因此不应该被设为主要优化方向。
性能分析日志中可以看到,由于我选用的类的类型内部包括多条字符记录,都以string类型存储,因此在过程中,针对string和w单词、词组类的空间赋值和转储消耗了大量时间。经过考虑,这是由于在编程过程中,为了图省事,过量分配空间,导致大量的转储操作,这个部分同样会被大量调用(5-6千万次)。当然,这是由于前文提及的一开始数据结构选择错误所导致的,等到重新选用unordered_map的时候已经来不及针对这个部分进行优化,但它显然可以成为未来进一步优化的方向。
最后,在这个过程中,结合热图可以看到,指令
str_test = *(word_temp1->word) + *(word_temp->word);
同样占据了大量的资源。经过查阅资料发现,这是由于运算符重载的方式是新生成一个对象给str_test,然后在调用类方法进行赋值,这样的方式看起来使代码简洁,但实际上降低了效率。
经过与同组同学的交流,张士龙(龙哥)给了我一个很好的建议,他与我一开始犯了同样的问题,之后他对其进行了优化,具体方式是将代码修改为如下:
str_test.append(*(word_temp1->word));
str_test.append(*(word_temp->word));
.......
str_test.clear();
经过测试,在我电脑上的linux平台和win平台上分别都有10%左右的提升,这也可以算是一个简单却又行之有效的提升了,在这个优化过程中,最重要的就是要去了解语言内部性能的瓶颈,然后再针对其进行相关的优化。
##Linux 性能分析日志(节选):
Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls ms/call ms/call name 23.97 9.12 9.12 51389593 0.00 0.00 **if (phrasemap.find(str_test) == phrasemap.end())** std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, phrase_count>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, phrase_count> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_find_before_node(unsigned long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long) const 14.60 14.67 5.55 49951455 0.00 0.00 **if (wordmap.find(str_test) == wordmap.end())** std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, word_count>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, word_count> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_find_before_node(unsigned long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long) const 10.49 18.66 3.99 64532537 0.00 0.00 //phrase_to_phrase()函数 中的phrase_count 空间的赋值和转储 std::__detail::_Equal_helper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, phrase_count>, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, unsigned long, true>::_S_equals(std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&, std::__detail::_Select1st const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long, std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, phrase_count>, true>*) 8.05 21.72 3.06 73828058 0.00 0.00 //word_to_word()函数 中的word_to_word 空间的赋值和转储 std::__detail::_Equal_helper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, word_count>, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, unsigned long, true>::_S_equals(std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&, std::__detail::_Select1st const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long, std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, word_count>, true>*) 2.95 26.44 1.12 **main** 2.84 27.52 1.08 183759572 0.00 0.00 **word_classifier::judge(char, word_count*)** 2.45 28.45 0.93 16650485 0.00 0.00 **freq_count(word_count*&, word_count*, int)** 1.84 29.15 0.70 16650187 0.00 0.00 **freq_countP(phrase_count*&, phrase_count*, int)** 0.62 32.42 0.24 16650485 0.00 0.00 **word_classifier::set(word_count*)** 0.54 32.84 0.21 53905831 0.00 0.00 **word_classifier::clear()** 0.50 33.03 0.19 101341048 0.00 0.00 **std::_Hash_impl::hash(void const*, unsigned long, unsigned long)** 0.49 33.22 0.19 98299918 0.00 0.00 **std::char_traits<char>::compare(char const*, char const*, unsigned long)** 0.29 34.32 0.11 19610082 0.00 0.00 word_to_word(word_count*, word_count*) 0.26 35.02 0.10 2959597 0.00 0.00 word_count::~word_count() 0.05 37.83 0.02 operator delete(void*, void*) 0.03 37.93 0.01 1439032 0.00 0.00 phrase_to_phrase(phrase_count*, phrase_count*) 0.03 38.00 0.01 2630 0.00 0.00 std::fpos<__mbstate_t>::operator long() const 0.03 38.01 0.01 20 0.50 1.33 fin_to_s(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&, int)