1.今天对分词函数做了优化,原来的分词函数记录了扫描过程中单词在输入字符串中的起始位置和结束位置,并调用标准库的substr函数从输入字符串中获取单词。但我新增了一个功能,就是通过一个bool指示变量来决定是否需要区分大小写,在这个背景下使用substr效率太低,因为我调用该函数,获取一个string,然后又要对这个string进行大小写转换生成另一个string;出现这个情况是因为无法干涉substr的执行过程,不能对单词中的每个字符进行大小写转换。
为了解决这个问题,我首先尝试调用string类的push_back函数逐个把扫描到的单词字符加入到对应的单词string中,我是预料这样每次从最后压入字符,应该是很快的,而且我也能在压入前进行大小写转换处理,可是数据表明我预计错了,竟然比原来慢了2.5倍左右!
上面的方法行不通,于是我决定自己来写substr的效果,这样写的话,要首先分配一个char数组,然后从输入字符串中把字符复制过来,中间可以插入大小写转换,然后调用string的assign函数构造最后的单词,这个方法比最初的标准库substr函数要有效,因为只需要两次转换就能得到最后的单词(string),并且经过网上搜索,我发现原来substr效率非常低下,一个高手用汇编版,比该函数快了20多倍(天哪,我也要学汇编调优!),然后我自己重写了这个获取子串的代码,结果还是比标准库的substr快了5%左右,并且获取了大小写转换的控制权。
调优还有上升空间,因为目前我是在每次发现单词后都动态分配一个char数组来进行中间缓冲,明天我会尝试把这个char数组固定下来,但这就需要对单词长度判断,如果超长了要重新分配,不过这个应该问题不大。
2.今天同样对另一个处理进行了修改,那就是保存扫描结果信息的HashSet,原来的版本没有动态扩容,一旦超出容量就异常。今天的目标就是实现扩容,Hash集合要扩容,消耗很大,因为它不能简单地把内部数据中的每个元素copy到新分配的内部数据中,因为hash码的计算涉及一个素数,这个素数一般就是内部数据容器的大小,当容器大小改变了,通过新素数计算出来的hash码就不能对应到就的数据索引中,从而导致错误,只能逐个集合元素重新计算hash码并放到新内部数据的对应位置。
起初我在扩容条件判断中,简单地判断当前集合元素个数是否等于内部数组大小(就是填满了),对于一般容器都可以这样判断是否需要扩容,但是我忘记了我是使用双重(多重)散列的hash集合,如果一个数组位置已经被占据了,那么就要进行另一次hash值计算来找下一个数组位置,如此重复直到找到空的为止,可想而知,随着可用的数组位置越来越少,就要进行越来越多的散列计算才能找到一个位置,我竟然忽略了这一点,因此在我一次调试中发现,在只剩下10%左右的可用数组位置时,要进行2百多万次hash值计算才找到一个空位置,难怪这么慢!于是我就把条件修改成百分比,当占用率达到50%后马上扩容,这个占用率很难定,50%不是绝对正确,因为扩容太频繁,性能也会降低。在这个过程有个小插曲,原来if(_count == _size * 0.5)这个计算是错的,因为_count会与一个浮点类型进行比较(我以为会自动进行截断再作为int比较,我错了)。
刚写到这里才发现,当我扩容后,内部数组的大小变为原来的两倍,这时候数据大小已经不是一个素数!难怪扩容后性能提升不太明显,因为使用了一个非素数来进行hash值计算,看来明天又有性能上调的空间了!