碎片式内存池的可行性分析
我这几天忙着开发内存池,对内存池的开发和应用小有体会。忙了几天,一个按照我的设想来开发的局部式的、可回收的内存池实现了。
虽然实现了,但却没有感到多少兴奋。不是由于这是小程序不值得高兴,也不是开发的内存池性能不佳。我发现,内存池也不是万能的。如果要使用的内存尺寸是固定的,或者虽然不是固定但只在一个不大的范围内变化,那么内存池可以发挥作用,对提高进程的处理效率有不少帮助。但如果要使用的内存尺寸在一个很大的范围内变动,那么内存池将会面临着向系统申请了过多的但不被充分使用的内存的情况。
举例说明,例如某个进程内的一个特定处理过程,需要使用从1K到100K大小的内存。那么现在有两种管理内存池的方式:
1、减小不同规格内存的尺寸差异,增加内存种类。例如设置内存池每种规格的内存尺寸相差100字节,从1K到100K,共1000种规格。这样,如果一个申请需要35.5K的内存,它都不可能去使用35.4K或35.6K的内存(可以专门开发以进行某种兼容,另外再谈),这样的结果就是内存池为各种规格的内存申请了大量内存,而这些不同规格的内存各自为政,不被有效使用。
这个概括为:减少不同规格内存的兼容。但不兼容实际导致了内存被浪费。
2、增大不同规格内存的尺寸差异,减少内存种类。例如设置每种规格内存相差10K,从10K到100K,共10种规格。在这种情况下,别说35.4、35.5或35.6K的内存,只要大于30K小于40K的内存,都用40K规格的内存。兼容性提高了,但30.1K的内存申请被分配了40K,内存又被浪费了。
可以说,这个矛盾是无法调和的。不同的设置只是解决了一个问题,但带来了另一个问题。
在这样的情况下,我想到了把内存进行分拆,再进行组合的办法。这种实现方式的内存池,暂时想不到合适名称,暂且成为碎片式内存池吧。下面我尝试说明什么是碎片式内存池。
为了表述方便,我们先来定义几个常用概念;
Mem:内存条,传统内存池向调用提供的内存使用的最小单位。例如,如果进程读取磁盘上的一个35K的文件并就此向内存池申请内存,内存池如果在特定设置下只能提供10K、20K、30K、40K、50K等规格的内存,那么它将提供40K的内存。这个40K的内存,就是Mem。
MemSize:不用说,就是40K了。
Block:内存块,一块通常包含若干条相同规格的Mem。
BlockSize:一个内存块包含多少条内存条Mem。
BlockMax:某种规格的内存块,最多可以向系统申请多少块。
那么传统内存池是这样的:
内存规格:提供10K、20K、30K、40K、50K、60K、70K、80K、90K、100K的内存。
如果调用向内存池申请35K内存,就向调用提供40K内存。这里浪费了5K内存。最大浪费为申请30.1K内存却提供40K内存,此时浪费接近10K。最小浪费为接近0K,平均浪费为5K。
碎片式内存池是这样的:
内存规格:只提供10K和1K两种规格。
当调用申请35K内存时,内存池将提供3条10K和5条1K内存,这样刚好就是35K。平均内存浪费仅为0.5K。
可见,碎片式内存池最大的特征就是节省内存。
由于碎片式内存池提供的内存不是一块连续内存,而是多块不连续内存,所以这些被分配内存的使用需要特殊方法。写入时,得一块块写。读取时,也要一块快读出。这样,在某些地方是不能使用这种碎片内存池的。例如,读取磁盘文件,传统上我们可以先分配一块内存,向系统API传入指针,一次性高效地读取文件。但碎片内存池不能这样使用。
碎片内存池分配的内存,必须有另外的内存配合才能使用。写入时,数据必须先写入另一个内存,然后才能分段地写入碎片内存。读取时,必须另外提供一块足够大的内存,然后分段地读出。这看起来不可思议,幸运的是,由于线程栈内存通常有1M内存,如果读写数据小于1M,那么用栈内存配合,栈内存的分配足够高效,影响性能的实际是不管读写,都需要一次额外的复制。这次额外复制的成本是衡量碎片内存池是否值得使用的主要因素。
碎片式内存池的应用场合,最适合于那些预先不知道数据大小,需要先缓冲到栈内存,然后再写入堆内存的场合,例如数据库。做数据缓存可以考虑碎片内存池来减少内存浪费。
不过,总的来说,碎片式内存池是个麻烦的东西。实现麻烦,使用尤其麻烦。但如果用传统内存池,当申请使用的内存尺寸变化很大时,传统内存池无法解决内存浪费的问题。
也许干脆不用池,直接向系统申请特定大小的内存怎样?碎片内存池也许最适合拿来同这种情况对比。碎片内存分配效率高,但读写需要多一次额外复制。
暂时想到的就这么多,大侠来谈谈看法吧。