3

术语“池”和“缓冲区”在这里可以互换使用。
假设我有一个我想在程序开始时分配的池,而不是一直调用new
现在,我不想人为地限制自己池的大小,但是如果我重新分配一个更大的池,所有指向旧池的指针都会失效,这当然不是很酷。


我想到的一种方法是“分页”,又名

const int NUM_PAGES = 5;
char* pool[NUM_PAGES];

并分配一个新页面,而不是只重新分配一个页面。这将使所有指针保持有效,但会使分页池的管理更加困难。另外,我限制自己的页数,所以最后再次限制池的大小。


另一种方法是从我的分配函数返回的指针映射到指向实际内存空间的指针。这将使所有旧指针保持有效,但会占用更多内存,并且我需要编写一个智能指针以从执行映射的分配函数返回。


还有哪些其他可能的方法来实现我想要的?在上面的示例实现中,我错过了哪些(不利)优势?

4

5 回答 5

2

扩展您的分页“池”概念,如果您将页面存储为链表呢?

要从池中分配新数据,您只需要访问顶部的“页面”,它将位于列表的头部,所以这是 O(1)。如果您需要增加池的总大小,请分配一个新页面并将其推送到列表的头部,也是 O(1)。

我对池分配器使用基本相同的想法,但也使用最近释放项目的“空闲列表”......

编辑:根据您的评论,如果您还想使用已释放的数据,您还可以存储一个空闲列表,也可能作为链接列表。因此,当您释放数据时,您会将指针和大小标记推送到空闲列表中。从池中分配数据时,首先检查是否可以使用空闲列表中的任何项目,如果没有,则从池中分配。

标准的内存管理器通常已经做了类似的事情,所以这种方法并不总是更好。具体来说,我通常只在分配的项目大小相同时才使用这种类型的自定义分配器(因此空闲列表的遍历是 O(1)!)。std::list 的自定义分配器就是一个例子。

希望这可以帮助。

于 2011-04-27T06:43:31.177 回答
2

你说的东西让我想起了std::deque. 我不确定您是否可以按std::deque原样使用,或者您是否只需要使用它的基本设计来实现某种分配器。

于 2011-04-27T07:05:51.770 回答
1

考虑使用提升池

于 2011-04-27T06:31:54.043 回答
1

当然,一个问题是,为什么要这么麻烦自己?

您说您希望避免new开销,但为什么不选择更好的实现new呢?tcmalloc并且jemalloc通常是多线程应用程序的非常好的竞争者。

实际上,您尝试创建的内容与编写自定义malloc/new实现非常相似。因此,如果您真的不想使用经过验证的实现,那么您将从那些使用过的人的见解中受益。

我个人的兴趣倾向于使用 BiBOP 策略(Big Bag of Pages)来对抗碎片化。每个分配大小都有一个专用池的想法,因此一个简单的空闲列表(与分配交错)就足够了(不需要合并)。通常,只要请求的大小小于页面大小(我已经看到使用 4KB)并且任何更大的东西都是自己分配的(在几个页面中),就会这样做。丢弃的页面被回收。

我的主要兴趣是使用 BiBOP 很容易维护间隔树地址范围 -> 页头,从而从(可能)内部元素(如属性)的地址确定对象的完整大小,这很有用用于垃圾收集(参考以下步骤)。

对于多线程分配,tcmalloc使用jemalloc两种不同的方法:

  • jemalloc使用每个线程池:使用固定数量的线程/线程池很好,但会使创建线程的过程成本更高
  • tcmalloc使用具有无锁技术的全局多线程池,并尝试对可用池上的线程进行负载平衡以限制争用,如果它使用的线程被“锁定”(而不是等待)并让每个线程将最后使用的池缓存在线程局部变量中。因此线程是轻量级的,但是如果池的数量相对于活动线程的数量太少,可能会出现一些争用。
于 2011-04-27T07:34:46.467 回答
0

一些想法:

  • 当您拥有 astd::vector<T*>时,添加元素并触发调整大小会使对该容器的引用/指针/迭代器无效,但不会使直接寻址指向对象的引用/指针无效。因此,一层间接可能会解决您的问题,具体取决于您真正尝试使用这些引用/指针/迭代器做什么。

  • 在具有虚拟内存和大地址空间的系统中,您可以进行大量分配,而无需实际从物理内存资源分配页面,直到它们被写入。因此,在这样的系统上,您可以为初始设置比以往更大的容量,vector而不会觉得您在浪费任何有价值的东西。

  • 其他容器 -std::map<>并且std::list<>- 在添加新元素时不会移动其现有元素,因此迭代器/指针/引用仍然有效。

于 2011-04-27T07:07:20.147 回答