前阵子看了一下网狐的内存池的实现,感觉这个内存池的实现简单明了,高效,数据包的加入与取出类似队列的原理,先进先出。不足之处是当在一段连续时间内只有数据放入内存池的动作,而且没有从内存池取出数据的动作,内存池中申请的内存不断的增大,每次申请更大内存时,就出现内存数据的拷贝,数据量越大,拷贝的内存数据量也就越大。所以这个内存池的实现应该比较适合生产者(产生数据放入内存池) 加入数据速率与 消费者(从内存池中取出数据进行后续处理)取出数据速率相对比较均衡的情况下,这样就能以占用最小的内存量达到最大的吐吞率,因为内存池的最大量会达到一定上限,不会不断扩大,并且减少了内存碎片。
下面具体分析一下这个内存池的基本实现原理,具体实现代码不贴出来,本文只是对实现原理进行分析。首先看一下内存池类CDataStorage 的主要成员变量:
DWORD m_dwDataSize; //内存池中数据的大小
DWORD m_dwBufferSize; //内存池的总大小
DWORD m_dwInsertPos; //当前数据加入的位置
DWORD m_dwTerminalPos; //当前数据的终点位置
DWORD m_dwDataQueryPos; //当前取出的数据位置
DWORD m_dwDataPacketCount; //内存池中数据包数是
BYTE * m_pDataStorageBuffer; //指向内存块的指针
下面分步对内存池的使用进行讨论:
1. 初始时,内存池的空间为0,
m_dwDataSize=0;
m_dwBufferSize=0;
m_dwInsertPos=0;
m_dwTerminalPos=0;
m_dwDataQueryPos=0;
m_dwDataPacketCount=0;
m_pDataStorageBuffer=NULL;
2. 假设有生产者把1K数据包加入内存池时,这时在堆上申请10K的大小的内存块。申请 10K是为了避免后面需要更大一点内存时,频繁的申请内存,降低效率和造成大量的内存碎片,当内存池不够时,会再原来的内存大小基础上申请更大的内存。
此时内存池如下:
m_dwDataSize= 1K;
m_dwBufferSize=10K;
m_dwInsertPos=1K;
m_dwTerminalPos=1K;
m_dwDataQueryPos=0;
m_dwDataPacketCount=1;
3. 消费者取出1K数据包, 这时各变量值如下:
m_dwDataSize= 0;
m_dwBufferSize=10K;
m_dwInsertPos=1K;
m_dwTerminalPos=1K;
m_dwDataQueryPos=1K;
m_dwDataPacketCount=0
4. 生产者放入2K的数据包,此时内存池情况如下:
m_dwDataSize= 2K;
m_dwBufferSize=10K;
m_dwInsertPos= 3K;
m_dwTerminalPos=3K;
m_dwDataQueryPos=1K;
m_dwDataPacketCount=1
5.生产者 再把11K的数据包放入内存池,由于内存池大小只有10K,且已经用了2K,所以这时申请更大的内存,假设申请20K,然后,把原来内存池的数据拷贝新申请的内存池中,到再把数据放入新内存中并释放掉旧的内存池,此时内存池情况如下:
m_dwDataSize= 13K;
m_dwBufferSize=20K;
m_dwInsertPos= 13K;
m_dwTerminalPos=23K;
m_dwDataQueryPos=0K;
m_dwDataPacketCount=2
6 消费者取出2K的数据包后,此时内存池情况如下:
m_dwDataSize= 11K;
m_dwBufferSize=20K;
m_dwInsertPos= 13K;
m_dwTerminalPos=13K;
m_dwDataQueryPos=2K;
m_dwDataPacketCount=1
7.生产者再放入8K的数据包,此时,内存池总共还有9K空闲内存,但是没有连续在一起 ,这时需要先移动内存,整理出足够的连续空间,再放入8K的数据包。
此时内存池情况如下:
m_dwDataSize= 19K;
m_dwBufferSize=20K;
m_dwInsertPos= 19K;
m_dwTerminalPos=19K;
m_dwDataQueryPos=0;
m_dwDataPacketCount=2
8.消费者再取出11K数据包后,此时内存池情况如下:
m_dwDataSize= 8K;
m_dwBufferSize=20K;
m_dwInsertPos= 19K;
m_dwTerminalPos=19K;
m_dwDataQueryPos=11K;
m_dwDataPacketCount=1
9. 生产者此时再要放入 9K的数据包时,此时内存池最前面有足够的空闲空间,如下:
m_dwDataSize= 17K;
m_dwBufferSize=20K;
m_dwInsertPos= 9K;
m_dwTerminalPos=19K;
m_dwDataQueryPos=11K;
m_dwDataPacketCount=2
10. 生产者再放入 2.5K的数据包 此时总共空闲空间足够大,但不连续,并且存在两个分离的数据包,数据包的取出是先进先出,所以必须再重新申请一块更大的连续空间,比如40K,并进行数据拷贝,释放旧内存池.
m_dwDataSize= 19.5K;
m_dwBufferSize=40K;
m_dwInsertPos= 19.5K;
m_dwTerminalPos=19.5K;
m_dwDataQueryPos=0;
m_dwDataPacketCount=3
以上10个步骤,基本上体现对内存池操作动作。可以看出,如果取出与加入数据包的速度一直保持相当的话,这个内存池可以保持占用较小必要的内存。因为只要有更大的需要,就会不断的申请内存,内存池只会不断增大,不会减小,直接进程结束才释放掉。如果某个短时间加入数据包操作很多,但没取出数据包,这时会申请的很大的内存,等数据包取出完后,后面的时间里取出与加入数据包的速度相当的话,那么就只是一直使用一点点内存的点,从而造成内存空间的浪费,这个模式就不适合这种情况了,不过,我想这种情况应该是很少的。