前言:
STL的确是好东西,可以让程序简洁,同时丰富的算法库可以减少程序出现BUG的几率。但其性能问题却是公认的最大缺点。因此针对一些特殊的应用场景,比如频繁访问的容器,如果要做到性能最大化,则有必要自己重写一些简洁轻量级的算法。
立项:
前一阵子研究的窗口命令行控件需要一个内置的文本编辑器,文本编辑器的特点就是内容会经常变化,会有大量的插入、删除、查找操作。原本的设计是用标准的vector容器来保存文本内容的。在遇到了性能问题以后,用VS2010的性能分析工具发现GDI是影响性能的根本原因,但是vector的频繁操作对影响的性能也不小,因此有必要一起优化一下。
分析:
文本编辑器的行为特征是基于光标的,即先把光标移动到要修改的地方,然后再进行连续的插入或者删除操作,而且多数是在文本的末尾进行操作。因此可以总结出来,编辑器的行为特征主要点:
1. 大部分的修改操作是在文本末尾进行的
2. 连续输入,即在相同位置连续的插入操作
第1点没有问题,因为在容器的最后进行插入和删除并不需要移动数据,不会太影响性能。而第2点则有很大的优化空间,一般情况保存文本最适合是用vector容器,每次插入和删除一个字符,意味着后面的文本都需要往后或往前移动一个字节。(不要说用list链表来存储文本数据,要不然查找会消耗掉所有的性能)。
设计:
既然大部分的修改都是在当前光标处连续修改,那么优化的思想就是在每次插入或删除数据的时候,在当前光标位置处预留出足够的空间,使得在同一个位置进行连续的插入或删除操作的时候不需要再进行数据的移动,加速后续的连续修改操作。这个预留的空间称为Gap。
举个实际的例子,假设vector容器有下面的初始化数据:
如果要在光标处插入一个字符,标准的vector算法是把后面的内容往后移动一个字节,然后在当前位置处插入一个字符:
可以预见,假如在当前光标再次输入一个字符,则后面的文本还需要再次被移动,这样性能就在不停地移动数据中消耗掉了。而gap_vector的做法是这样,如果需要在当前光标处插入一个字符,则后面的文本会往后移动一大段,保证当前光标处预留出足够的空间:
如果后续再进行连续的插入操作,则不需要再移动数据,直接在Gap中进行插入即可:
在文本最后的插入操作也不需要移动文本,直接在文本最后插入即可:
仅仅在Gap空间不足的情况下,比如现在Gap空间只剩下23个字节,而我们需要在当前光标处粘贴一个32字节的文本,这时候需要重新调整Gap的空间:
当调整出足够的空间后,再在当前光标处插入文本:
从上面的例子可以看出,Gap预留空间的原则是,Gap大小 = 文本最后空闲空间的大小,这样可以同时兼顾当前光标处和文件最末处的文本修改操作。
代码:
#define GAP_VECTOR_GROW_SIZE 1024
template
class gap_vector
{
public:
gap_vector() : _body(NULL), _body_size(0),_grow_size(GAP_VECTOR_GROW_SIZE)
{
clear();
}
~gap_vector()
{
clear();
if(_body)
{
delete[] _body;
_body = NULL;
}
}
// FUNCTIONclear(): clear vector without changing the buffer size
voidclear()
{
_len_before_gap = 0;
_len_after_gap = 0;
_len_content = 0;
_len_gap = 0;
_gap_pos = 0;
}
// FUNCTIONset_grow_size(): modify auto-growth size of the buffer
voidset_grow_size(int grow_size)
{
_grow_size = max(1, grow_size);
}
// FUNCTIONresize_buffer(): resize the buffer
boolresize_buffer(int size)
{
if(size> _body_size)
{
T* new_body = new T [size];
if(NULL== new_body)
{
returnfalse;
}
memcpy(new_body, _body, _body_size* sizeof(T));
_body_size = size;
delete[] _body;
_body = new_body;
}
return true;
}
// FUNCTION resize():resize the vector
boolresize(int size)
{
// extend atthe end of content
gap_at(_len_content, size -_len_content);
_len_before_gap += size - _len_content;
_len_gap -= size - _len_content;
_len_content += size - _len_content;
return true;
}
// FUNCTIONgap_at(): move the gap position
boolgap_at(int pos,introom)
{
// grow sizewhen no enough room
while(room+ _len_content > _body_size)
{
if(!resize_buffer(_body_size+ _grow_size))
{
returnfalse;
}
}
// right gapposition and enough room
if(_len_before_gap== pos && _len_gap >= room)
{
return true;
}
if(_len_gap< room)
{
// new_gap_pos = _len_before_gap + room + (_body_size - _len_before_gap - room -_len_after_gap) / 2
intnew_gap_pos = (_body_size + _len_before_gap + room - _len_after_gap) / 2;
if(new_gap_pos+ _len_after_gap > _body_size) {returnfalse; } // not enough room
memmove(_body + new_gap_pos, _body+ _gap_pos, (_len_after_gap) *sizeof(T));
_gap_pos = new_gap_pos;
_len_gap = _gap_pos -_len_before_gap;
}
//BBB*BBB AAAAAA => BBB BBBAAAAAA
if(pos< _len_before_gap)
{
memmove(_body + _gap_pos -_len_before_gap + pos, _body + pos, (_len_before_gap - pos) *sizeof(T));
}
//BBBBBB AAA*AAA => BBBBBBAAA AAA
else
{
memmove(_body + _len_before_gap,_body + _gap_pos, (pos - _len_before_gap) *sizeof(T));
}
_len_before_gap = pos;
_len_after_gap = _len_content - pos;
_gap_pos = pos + _len_gap;
return true;
}
// FUNCTIONinsert(): insert content to pos
boolinsert(int pos, T* pt,intsize)
{
// insert atthe end and with enough room
if(pos== _len_content && _len_gap + _len_content + size <= _body_size)
{
memcpy(_body + _len_gap +_len_content, pt, size * sizeof(T));
_len_content += size;
_len_after_gap += size;
returntrue;
}
// move gapand ensure enough room for new cells
if(!gap_at(pos,size))
{
returnfalse;
}
// insert atpos
memcpy(_body + pos, pt, size * sizeof(T));
_len_before_gap += size;
_len_content += size;
_len_gap -= size;
return true;
}
// FUNCTIONremove(): remove content from pos
voidremove(int pos,intlength = 1)
{
intremove_len = max(0, min(_len_content - pos, length));
if(!gap_at(pos+ remove_len, 0))
{
return;
}
_len_before_gap -= remove_len;
_len_content -= remove_len;
_len_gap += remove_len;
}
// OPERATOR =:copy content from source vector
void operator = (gap_vector &src)
{
clear();
set_buffer_size(src.length());
for(int i = 0; i < src.length(); ++i)
{
insert(i, &src[i], 1);
}
}
inline int buffer_size() { return _body_size; } // get buffersize
inline int length() { return _len_content; } // get vectorlength
inlineT& back() {return_body[_len_content + _len_gap - 1]; } // get last cell
inline bool push_back(T& t) { returninsert(_len_content, &t, 1); } // push cell to the end
inlineT& operator [] (intn) { if(n<_len_before_gap)return *(_body+n); elsereturn *(_body+n+_len_gap); }
private:
T *_body; // head pointof vector buffer
int _body_size; //current buffer size
int _grow_size; //vector buffer growth size
int _len_before_gap; // text isdevided into two parts by the gap, this is the length of first part
int _len_after_gap; // text isdevided into two parts by the gap, this is the length of second part
int _gap_pos; // gapend position
int _len_content; //_len_content = _len_before_gap + _len_after_gap
int _len_gap; //_len_gap = _gap_pos - _len_before_gap
};