如何高效的管理缓存?--LoopBuffer

咱们须要一种缓存结构,能够未预知数据大小的状况下高效的管理内存。每次数据到来的时候都能保证有效的写入,即便动态的扩展内存也不会对原有的数据进行任何挪移操做。读取数据的时候只能顺序的读取,也不会对未读取到的数据进行移动。git

CppNet的数据流缓冲经过CBuffer类来实现,实际的数据存储在CLoopBuffer中,loop buffer实现如其名,经过在一块固定大小的内存上移动指针来实现顺序的读写操做。github

每一个loop buffer都持有一块来自内存池的固定大小的内存。而后经过四个指针来严格标识数据的位置,注意这里是严格标识,因此咱们申请到的内存不用memset初始化,每次读写经过移动指针来控制数据流动,下面着重说下指针的几种移动状况:缓存

start : 指向分配内存的起始地址。
end: 指向分配内存的末端地址。
read: 当前读取游标。
write: 当前写入游标。
当loop buffer第一次被建立时,指针的位置如图1:oop

图1

start, read, write三个指针都指向内存的起始位置,这个时候 read = write 可读取数据为空。接下来进行数据写入,如图2:性能

图2

write指针开始向右移动,记录着下一次写入的位置。如今可读取的数据量是 write - read, 剩余可写入的内存大小是 end - write。优化

接下来 咱们进行一次数据读取, 如图3:3d

图3

read 指针开始向右移动,读取到的数据量是 read - start,剩余可读取数据大小是 write - read,剩余可写入的内存大小是 end - write + (read - start)。指针

接下来咱们将全部的数据读取出来, 如图4:cdn

图4

read 指针向右移动直到追上了write,如今read == write,当读写指针相等的时候,有两种状况,要么是内存被写满,要么是内存块为空,须要一个额外的成员变量来标识。如今read追上了write,内存块为空,可读取数据大小是0,可写大小是整个内存块的大小,为了使可写缓存更为完整,以方便writev和readv的调用,每次read指针追上write指针的时候,咱们都将全部指针状态重置,恢复到图1的状态。blog

接下来又有新的数据到来, 如图5:

图5

咱们看到 write 到了read 的左边,这是由于write 一直向右移动的时候,当指向了 end 指针,则须要从新调整指向 start,这就是loop的由来,而此时read 和 start之间有很多的距离,咱们接着从start开始写入数据,write又从新开始向右移动。如今可读数据大小是 end - read + (write - start), 可写数据大小是 read - write。

若是接下来仍是数据写入的话, write 就会向右移动一直追上 read。这时 read == write, 可是内存已经被写满了。

为了配合readv的调用,须要有一个接口能返回当前可写内存的起始位置和大小,经过上述的几个过程咱们能够观察到有两种状况:

1> 图1,图2,图5的时候(图4状态会被重置为图1),只有一个可写缓存区,起始地址是write指针,长度是read - write 或 end - write。
2> 图3的时候有两个可写区域,起始地址是write和start,可写长度分别是end - write 和 read - start。 writev时须要返回全部的数据区域,与上述状况相似但操做的指针恰好相反,再也不详述。

以上的几个过程就是loop buffer写入和读取的所有状况,能够看到每次数据写入和读取的时候只有必要数据的复制,并无对其余数据的移动拷贝操做,并且每次数据流动的时候,都不会超出限定的内存区域。

可是loop buffer只有固定大小的内存,如果写满了以后还有新的数据写入请求怎么办?这就是buffer表演的时候了。

CBuffer实现上其实和CLoopBuffer很是的类似,也是经过四个指针来控制数据的读取和写入,甚至每一个指针的做用都与其相同,只不过CLoopBuffer中指针指向的内存块的具体位置,而CBuffer中的指针指向的是CLoopBuffer内存块。其内部经过一个单向链表管理全部的内存块节点,当有数据未满的时候,几个指针的移动操做和loop buffer的指针彻底相同。
惟一不一样的是,当全部的内存块被写满的时候,read == write,这时CBuffer须要从新从内存池中申请新的内存块,并将其添加到链表中。

以上的实现方式存在一个问题,CLoopBuffer的指针不是顺序申请的,没法经过比较指针地址来判断读写的前后顺序,因此每一个CLoopBuffer在实现的时候都携带了一个自身所处队列的索引,每次查找的时候都须要重载操做符<或>的调用来判断顺序关系,valgrind性能分析时发现这里调用频次极高,因此从新优化了CBuffer的实现。

重构以后的CBuffer用一个单向链表来管理loop buffer,写入数据的时候如何空间不够,则从内存池中申请新的节点添加到链表后边,write指针向后移动。读取数据的时候,一旦当前loop buffer节点的数据所有读取完成,则将当前块归还给内存池,read指针向后移动。实现起来像是红白机游戏里的马里奥过浮桥,每次踩过的砖块都会析构掉,前边会生成新的砖块拼成浮桥。整个读写过程都是从左往右顺序移动的过程。

以上就是CppNet缓存管理的核心实现。

github请戳这里