术语'pool'和'buffer'在这里可以互换使用。
假设我想在程序开头分配一个池,并不总是一直调用new
。
现在,我不想人为地限制自己的游泳池大小,但是如果我重新分配一个更大的游泳池,所有指向旧游戏的指针都会失效,这当然不是很酷。
我想到的一种方式是“分页”,又名
const int NUM_PAGES = 5;
char* pool[NUM_PAGES];
并分配新页面而不是仅重新分配一个页面。这将使所有指针保持有效,但使页面缓冲池的管理更加困难。另外,我限制自己的页数,所以最后还是在游泳池的大小上。
另一种方法是从指针映射我的分配函数返回指向实际内存空间的指针。这会让所有旧指针保持有效,但会占用更多内存,我需要编写一个智能指针,从我的分配函数返回来进行映射。
有哪些其他可能实现我想要的方法?我在上面的示例实现中错过了什么(dis)优势?
答案 0 :(得分:2)
扩展您的分页“池”概念,如果您将页面存储为链接列表怎么办?
要从池中分配新数据,您只需访问顶部“页面”,该页面位于列表的开头,因此为O(1)。如果您需要增加池的总大小,请分配一个新页面并将其推送到列表的头部,也是O(1)。
我基本上对集合分配器使用相同的想法,但也使用最近解除分配的项目的“免费列表”......
编辑: 根据您的评论,如果您还想使用解除分配的数据,您还可以存储空闲列表,也可以存储为链接列表。因此,当您解除分配数据时,将指针和大小标记推送到空闲列表中。从池中分配数据时,首先要检查是否可以使用空闲列表中的任何项目,如果没有,则从池中分配。
标准内存管理器通常已经做了类似的事情,因此这种方法不会总是更好。具体来说,当分配的项目大小相同时,我通常只使用这种类型的自定义分配器(因此空闲列表的遍历为O(1)!)。 std :: list的自定义分配器就是一个例子。
希望这有帮助。
答案 1 :(得分:2)
你在谈论让我想起std::deque
的事情。我不确定你是否可以按原样使用std::deque
,或者你只需要使用它的基本设计来实现某种分配器。
答案 2 :(得分:1)
考虑使用boost pools
答案 3 :(得分:1)
当然,有一个问题是为什么这么麻烦?
您说您希望避免new
开销,但为什么不选择更好的new
实施?例如,tcmalloc
和jemalloc
通常是多线程应用程序的非常好的竞争者。
您尝试创建的内容与编写自定义malloc
/ new
实现非常相似。因此,如果您真的不想使用经过验证的实施,那么您将从那些人的见解中受益。
我的个人兴趣倾向于BiBOP策略(Big Bag of Pages)以抵御碎片化。想法是每个分配大小有一个专用池,因此一个简单的空闲列表(与分配交错)就足够了(不需要合并)。通常只要请求的大小小于页面大小(我已经看过4KB)就可以完成,并且自己分配更大的内容(在几页中)。丢弃的页面被回收。
我的主要兴趣是使用BiBOP很容易维持间隔树地址范围 - >页眉,从而根据(可能的)内部元素(如属性)的地址确定对象的完整大小,这对垃圾收集很有用(参考后面的步骤)。
对于多线程分配,tcmalloc
和jemalloc
使用两种不同的方法:
jemalloc
使用每个线程池:对于固定数量的线程/线程池很好,但是创建线程的过程更加昂贵tcmalloc
使用具有无锁技术的全局多线程池,并尝试通过让线程查找新池来限制争用来对可用池上的线程进行负载平衡。使用是“锁定”(而不是等待)并让每个线程在线程局部变量中缓存最后使用的池。因此线程是轻量级的,但如果活动线程的数量太多,池可能会有一些争用。答案 4 :(得分:0)
一些想法:
当你有std::vector<T*>
时,添加元素并触发resize会使引用/指针/迭代器无效到该容器中,但不会使直接寻址指向对象的引用/指针无效。因此,一个间接层可能会解决您的问题,具体取决于您真正尝试使用这些引用/指针/迭代器。
在具有虚拟内存和大地址空间的系统中,您可以进行大量分配,而不会实际从物理内存资源中分配页面,直到它们被写入。因此,在这样的系统上你可以为vector
创建一个比以往更大的容量,而不会觉得你在浪费任何有价值的东西。
其他容器 - std::map<>
和std::list<>
- 在添加新元素时不要移动现有元素,因此迭代器/指针/引用仍然有效。