碎片式内存池的可行性分析

z1z2z3z4 2009-01-13 10:47:54
我这几天忙着开发内存池,对内存池的开发和应用小有体会。忙了几天,一个按照我的设想来开发的局部式的、可回收的内存池实现了。

虽然实现了,但却没有感到多少兴奋。不是由于这是小程序不值得高兴,也不是开发的内存池性能不佳。我发现,内存池也不是万能的。如果要使用的内存尺寸是固定的,或者虽然不是固定但只在一个不大的范围内变化,那么内存池可以发挥作用,对提高进程的处理效率有不少帮助。但如果要使用的内存尺寸在一个很大的范围内变动,那么内存池将会面临着向系统申请了过多的但不被充分使用的内存的情况。

举例说明,例如某个进程内的一个特定处理过程,需要使用从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,那么用栈内存配合,栈内存的分配足够高效,影响性能的实际是不管读写,都需要一次额外的复制。这次额外复制的成本是衡量碎片内存池是否值得使用的主要因素。

碎片式内存池的应用场合,最适合于那些预先不知道数据大小,需要先缓冲到栈内存,然后再写入堆内存的场合,例如数据库。做数据缓存可以考虑碎片内存池来减少内存浪费。

不过,总的来说,碎片式内存池是个麻烦的东西。实现麻烦,使用尤其麻烦。但如果用传统内存池,当申请使用的内存尺寸变化很大时,传统内存池无法解决内存浪费的问题。

也许干脆不用池,直接向系统申请特定大小的内存怎样?碎片内存池也许最适合拿来同这种情况对比。碎片内存分配效率高,但读写需要多一次额外复制。

暂时想到的就这么多,大侠来谈谈看法吧。
...全文
1115 38 打赏 收藏 转发到动态 举报
写回复
用AI写文章
38 条回复
切换为时间正序
请发表友善的回复…
发表回复
「已注销」 2010-07-24
  • 打赏
  • 举报
回复
变长内存池,没有什么更好的算法, 粗粒度,细粒度都会造成浪费,lz的碎片内存池,提法好,但实现麻烦,且效率不高
tanker1024 2009-11-23
  • 打赏
  • 举报
回复
做个记号 以后参考
edd 2009-02-10
  • 打赏
  • 举报
回复
整理?整理的结果就是,以前的指针全都变了。那也就是说,你的内存不能用指针的保留下来继续使用,必须每次使用都用一个接口获得内存指针,而且还得加锁,防止多线程环境中访问冲突,这种东西有使用价值吗?别人会用吗?
z1z2z3z4 2009-02-10
  • 打赏
  • 举报
回复
edd

一点愚见:
如果不用池,的确很快就无法分配20或30K内存了。
如果用传统内存池,大概的思路应该是划定一块30K内存供20K和30K用。但明显地,划分一块30K太浪费了。
这种情况,如果用碎片内存池很好解决。具体做法是把全部60K内存分为20块3K,对于2K和3K的图片可直接分配使用一块,对于20K或30K图片则使用多块组合。
如果图片除了2K、3K、20K和30K规格外还有1K和10K的等等,那么把总60K内存分为60块1K,组合使用即可。
wyc761024 2009-02-06
  • 打赏
  • 举报
回复
内存池的使用应该是很重要的,可惜我现在的工作没有接触这块,但是我想内存池的机制是一样的,一般的使用内存池是在频繁的申请和释放内存,希望内存碎片较小,和内存容量小的情况下使用。
频繁申请和释放内存使用是侧重于解决效率问题,在重载了系统的new和delete方法后直接从程序的内存池申请释放效率应该是相当高,依据实现的数据结构大概最后都会转化为几条指针赋值操作。
希望内存碎片较小和内存容量小使用侧重于保障程序能顺畅的运行,甚至是保障程序能继续运行,这个时候如果依靠数据结构依然不能有效地解决问题,那么效率就不那么重要了,因为涉及到碎片的拼接和数据的有序完整等等问题。
没有施展经验,如果我说错了,请不要笑话我。
edd 2009-02-06
  • 打赏
  • 举报
回复
28楼的,你连楼主的内存池怎么用都没有看明白,就来回帖了,你看明白了再说好不好?

楼主,因为我前段时间遇到过一个程序,不用内存池功能简直没法实现,所以我才看到你这个内存池的贴很有兴趣。
但是我发现你这个内存池解决不了我前段时间遇到的问题,你这个内存池限制太多了,只能用于特定的情况。

我遇到的问题是这样,在一个内存很小的机器上作游戏(其实就是symbian操作系统)。我试验了一下,内存一共大概可以分配60k,但是一个图片一般是一个大图片有20k,30k,还有一堆小图片2k,3k的。如果不用内存池,只要分配两三次,产生的内存碎片就可能让20k,30k的图片没有空间分配了,表现就是运行一会儿之后忽然报告内存不足。

当时我自己实现了一个内存池解决这个问题。我还以为你这个内存池对我有借鉴作用,不过看了之后完全不是我想要的东西,不过这种思路也许在某些时候会有些帮助,我暂时没有想到什么地方能用到。
naturemickey 2009-02-06
  • 打赏
  • 举报
回复
[Quote=引用 31 楼 edd 的回复:]
28楼的,你连楼主的内存池怎么用都没有看明白,就来回帖了,你看明白了再说好不好?
[/Quote]不和你吵!没啥意思!

[Quote=引用 31 楼 edd 的回复:]
楼主,因为我前段时间遇到过一个程序,不用内存池功能简直没法实现,所以我才看到你这个内存池的贴很有兴趣。
但是我发现你这个内存池解决不了我前段时间遇到的问题,你这个内存池限制太多了,只能用于特定的情况。

我遇到的问题是这样,在一个内存很小的机器上作游戏(其实就是symbian操作系统)。我试验了一下,内存一共大概可以分配60k…
[/Quote]写一个内存管理模块,内在管理中有一个碎片整理模块。
在分配内存失败的时候,整理一次内存,并再次分配,若再次失败就比较麻烦了:要么就是实现得有问题,要么就是内存实在太小,如果是后者,可以考虑压缩图片,也可以考虑写一个虚拟内存。

内存管理与内存池在概念上是有区别的。
对象池是通过缓存对象来提高性能的,其它方面是相对次要的,内存池是对象池与适配器两者的结合。
你的平台内存太小,不适合做缓存。
edd 2009-02-03
  • 打赏
  • 举报
回复
关键是你的东西到底有什么用。
比如我本来要分配10k空间,我不用你的内存池,我的做法是new一个10k,然后delete掉。
用你的,我需要先放到内存池,然后要从内存池读出来,还是要new一个10k,然后把内存池读出来的东西放到这10k中,用完了在再把我这10k内存delete掉。
这中间到底有什么好处?我没有看明白。
z1z2z3z4 2009-02-03
  • 打赏
  • 举报
回复
碎片式内存池可以避免额外的复制,碎片内存的读写性能可以做到接近于连续内存(传统内存池分配的或用系统new的)——

在多数情况下,内存池的数据是用于和其它数据合成的,在这样的情况下,不需要分配专门的栈内存用于辅助读出碎片内存的数据,直接读取碎片内存的数据到预先分配的内存即可,这样,就不存在额外的复制开销。

现在假设内存池的数据是用于合成,我们来看看碎片内存池和传统内存池或系统new内存的读取方式的差异和性能差异:

假设调用申请35K内存:
传统内存池或系统new将分配一块35K连续内存。
碎片内存池分配的是10K+10K+10K+1K+1K+1K+1K+1K的内存组合。

然后调用向分配的内存写入数据。

现在从这个内存读出数据(这里讨论读出的数据用于合成的情况):
1、分配一块足够大的内存用于储存合成数据,堆内存或栈内存均可。假设分配500K大内存。
2、读出内存池数据到大内存上:
(1)传统内存池或系统new的内存:通过指针可以高效地把35K连续内存的数据读出到大内存。
(2)碎片内存池:需要对10K+10K+10K+1K+1K+1K+1K+1K内存分别读出数据到大内存。

可见,性能的差异主要在于,碎片内存池的内存,读出数据要从多块内存读出。以读出35K数据的例子来说,CPU开销主要在于35K数据的读出(就是复制了),至于碎片内存要读取多块内存由此引起的性能损失,以我的经验,可以做到损失小于1/1000。

所以,碎片式内存池在特定情况下还是可以保证性能的。
z1z2z3z4 2009-02-03
  • 打赏
  • 举报
回复
[Quote=引用 27 楼 edd 的回复:]
用你的,我需要先放到内存池,然后要从内存池读出来,还是要new一个10k,然后把内存池读出来的东西放到这10k中,用完了在再把我这10k内存delete掉。
这中间到底有什么好处?我没有看明白。
[/Quote]
假设调用向内存池申请35K内存,碎片式内存池向调用提供10K+10K+10K+1K+1K+1K+1K+1K的内存组合。然后调用给这些碎片内存写入数据。

你现在的问题是如何读取这个碎片内存的数据。

有两种办法读取碎片内存的数据:

1、第一种办法:分配一个35K的栈内存(不要用new分配,new分配的是堆内存)。然后把碎片内存的数据读出到栈内存。用完后,栈内存不用释放。可行性是:栈内存的分配速度极快,几乎不耗费时间。你只要关注从碎片内存读出到栈内存的复制开销即可。缺点是栈内存通常只有1M或2M。

2、第二种办法:如果碎片内存的数据是用于和其它数据合成,那么你读取碎片内存的数据前肯定已经分配了足够大的内存(不管堆的栈的都行)。在这样的情况下,读出碎片内存的数据不用分配专门的35K栈内存,只要直接读出到已经分配的大内存即可。

写入碎片内存的情况也一样。
z1z2z3z4 2009-02-03
  • 打赏
  • 举报
回复
ugg

通常开发的内存池,向调用分配的内存是一块连续内存。这里把这种内存池称为传统内存池。本文的碎片式内存池的特点是,分配的内存不是连续内存,而是多块不连续的内存的组合。

你链接的内存池和测试我看了,我正好缺乏多线程尤其多核情况下的测试。
不过,你好像没有测试释放free/delete。在我的测试中,对于8-128字节的内存的malloc/new,速度还好,但free/delete速度很差。在单线程下,内存池的分配比系统malloc/new快几十倍,释放则比系统free/delete快几百倍。

对于较大的内存的申请和释放(测试了5K到500K),我测试的情况是,(和小内存的系统malloc/free相比)free/delete的时间差不多,但系统malloc/new需要的时间大幅上升,所以和内存池的分配速度也拉开了较大的距离。

多线程多核情况怎样,还没测试。

本文提出的碎片式内存池,我不能肯定利大于弊还是弊大于利。主要是本文提出的这种“需要的内存尺寸在一个很大的范围内变动”的内存需求下,传统内存池不论如何设置,都会浪费较多的内存,我目前还没有好的解决办法。
naturemickey 2009-02-03
  • 打赏
  • 举报
回复
[Quote=引用 27 楼 edd 的回复:]
关键是你的东西到底有什么用。
比如我本来要分配10k空间,我不用你的内存池,我的做法是new一个10k,然后delete掉。
用你的,我需要先放到内存池,然后要从内存池读出来,还是要new一个10k,然后把内存池读出来的东西放到这10k中,用完了在再把我这10k内存delete掉。
这中间到底有什么好处?我没有看明白。

[/Quote]怀疑你没有用过对象池。内存池帮你管理内存的读、写、分配、回收(注意:我没有用“释放”两个字),也就是说,自己写了一个新的NEW和DELETE操作,不再使用系统函数new或malloc了,至于内存池怎么去做,是内存池的事。用户不用去管那些(用户只要学会使用内存池的接口就行了)。

另外,楼主只说在读写文件时会有读写有额外复制的问题(并且这个问题也是由内存池来管理的,对用户来说也是不可见的)。

我的观点在前面也说过了,根据我工作涉及到的系统来看,我是用不到这样的内存池的,所以我也片面的认为没有必要开发这样的内存池,我们或许可以用其它方法避免楼主所说的问题的出现。

同时,任何实现都会带来一定的问题,没有完美的东西。提出问题很容易,重要的是解决问题。我们要为CSDN多做点贡献的意思是:我们要多解决问题,而不仅仅是多提出问题。
pluminsnow 2009-02-02
  • 打赏
  • 举报
回复
[Quote=引用 21 楼 naturemickey 的回复:]
应该顶一下。
CSDN上很少有真正创造生产力的家伙到这里来发一些有实际意义的问题,可是大家都不怎么热情啊!
结帖之前,别让这个帖子沉下去!
[/Quote]

顶一个
逸学堂 2009-02-02
  • 打赏
  • 举报
回复
我的几点理解:
1:内存池,我认为没有标准,传统之分。所以不清楚lz说的什么样的内存池是标准的,什么样的是传统的。
2:设计内存池的目的:
(1): 增加内存分配释放速度这只是内存池的一部分功能。之前我采用sgi
STL中alloc内存池机制进行过测试,在单核windows下,alloc内存池机制性能比较显著,而在多核的windows下
内存池机制反而没有直接malloc/free快,这是因为windows在多核下对malloc/free做了特殊优化(具体可以参考
msdn,资源竞争方面的优化)。而一般我们做服务器的主机都是多核系统,所以从性能上来说,在使用内存池还
是要慎重。
(2):减少内存碎片,程序长时间运行,倒是程序的内存碎片逐渐增加,程序运行越来越慢,而使用内存池可以减少
内存碎片的产生,可以保证程序长时间有效运行。而内存碎片的产生主要是频繁申请小内存造成,所以sgi的stl中
的内存池对于大于128字节,直接采用malloc/free。只是对于小于128字节的申请采用内存池方式。而lz的k级的
内存申请和释放,如果是特别频繁的话,是不会产生大量内存碎片的。

我之前写的一个内存池实现,以及测试数据参考
http://blog.csdn.net/ugg/archive/2007/03/27/1543290.aspx
naturemickey 2009-02-02
  • 打赏
  • 举报
回复
[Quote=引用 22 楼 edd 的回复:]
不明白,这个东西似乎没用啊。比如你分配100k内存,就需要存放两份,一份放在你的内存池里面,一份放在你读取的时候分配的连续空间里面。
这样没有意义啊,我直接new或者free了,比你还少一份存放的空间消耗。

读取时,必须另外提供一块足够大的内存,然后分段地读出
你这一步似乎是脱裤子放屁,多此一举阿。你直接分配一块内存就可以了,何必先把他放到一个什么内存池,在分配一块空间把他读出来。
也就是说,必要的内存空…
[/Quote]
乱扣帽子!
edd 2009-02-01
  • 打赏
  • 举报
回复
不明白,这个东西似乎没用啊。比如你分配100k内存,就需要存放两份,一份放在你的内存池里面,一份放在你读取的时候分配的连续空间里面。
这样没有意义啊,我直接new或者free了,比你还少一份存放的空间消耗。

读取时,必须另外提供一块足够大的内存,然后分段地读出
你这一步似乎是脱裤子放屁,多此一举阿。你直接分配一块内存就可以了,何必先把他放到一个什么内存池,在分配一块空间把他读出来。
也就是说,必要的内存空间还是要分配的,而且还多了一个内存池的消耗。
naturemickey 2009-01-29
  • 打赏
  • 举报
回复
应该顶一下。
CSDN上很少有真正创造生产力的家伙到这里来发一些有实际意义的问题,可是大家都不怎么热情啊!
结帖之前,别让这个帖子沉下去!
naturemickey 2009-01-25
  • 打赏
  • 举报
回复
[Quote=引用 19 楼 dongpy 的回复:]
程序设计中,根据实际需求,可以用时间换空间或者用空间换时间.

楼主的碎片内存池是时间换空间的做法,但最大问题是,内存池里的内存不能直接访问,需要有个临时缓冲区,除了效率降低外,灵活性也是个问题,这个时候内存池更像是磁盘而不是内存了.
访问内存池,要导出数据到临时缓冲区(或从缓冲区导入),有点像是读写文件了,而内存池的管理也有点文件系统的意思了.

为了内存使用率牺牲这么多,我觉得不太值得.其实C库里的堆内存管…
[/Quote]
这样的内存池不是用来直接访问的,内存读写完全由内存管理程序来做,就像一个虚拟机(由虚拟机管理内存,而不是由程序员来管理)。
不需要有临时缓冲区也是可以的。
只是在内存读写的时候会有一点慢而已。
对于频繁的内存分配的程序来说是不错的选择,因为malloc对于频繁的小块分配来说,效率是比较低的,尤其在windows下。

楼主的想法对于那些内存样式繁多,并且内存分配频繁的程序来说是非常好的(比一般的内存池好,也比malloc好)。
但仅仅在这种情况下比较好。
对一我来说,我没见来内存样式特别多的情况(我们可以通过某些模式尽量减少类的数量——面向对象的程序来说),所以我倾向于用普通的内存池。
我几乎没有做过OO以外类型的程序,不清楚其它情况。
dongpy 2009-01-23
  • 打赏
  • 举报
回复
程序设计中,根据实际需求,可以用时间换空间或者用空间换时间.

楼主的碎片内存池是时间换空间的做法,但最大问题是,内存池里的内存不能直接访问,需要有个临时缓冲区,除了效率降低外,灵活性也是个问题,这个时候内存池更像是磁盘而不是内存了.
访问内存池,要导出数据到临时缓冲区(或从缓冲区导入),有点像是读写文件了,而内存池的管理也有点文件系统的意思了.

为了内存使用率牺牲这么多,我觉得不太值得.其实C库里的堆内存管理器提供的malloc/free操作,性能还是很高的(包括内存使用率),当然系统不支持C库的,另当别论.
naturemickey 2009-01-20
  • 打赏
  • 举报
回复
up
加载更多回复(16)

33,317

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 新手乐园
社区管理员
  • 新手乐园社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧