分配/释放大量小对象的策略

时间:2010-03-13 18:44:57

标签: c++ memory-management memory-fragmentation

我正在使用某些缓存算法,这在某种程度上具有挑战性。 基本上,它需要分配许多小对象(双数组,1到256个元素),对象可通过映射值map[key] = array访问。初始化数组的时间可能非常大,一般超过1万个cpu周期。

通过很多我的意思是总共大约千兆字节。可能需要根据需要弹出/推送对象,通常在随机位置,一次一个对象。对象的生命周期通常很长,几分钟或更长,但是,在程序持续期间,对象可能会多次分配/释放。

什么是避免内存碎片的好策略,同时仍然保持合理的分配解除分配速度?

我正在使用C ++,所以我可以使用new和malloc。 感谢。

我知道网站上有类似的问题Efficiently allocating many short-lived small objects,有些不同,线程安全对我来说不是直接问题。

我的开发平台是Intel Xeon,linux操作系统。 理想情况下,我也想在PPC linux上工作,但对我来说这不是最重要的。

3 个答案:

答案 0 :(得分:6)

创建一个分段分配器:

分配器是使用多页内存创建的,每个内存大小相等(512k,256k,大小应根据您的需要调整)。

对象第一次向此分配器请求内存时,它会分配一个页面。分配页面包括将其从空闲列表中删除(不搜索,所有页面大小相同)并设置将在此页面上分配的对象的大小。通常,这个大小是通过获取请求的大小并将其四舍五入到最接近的2的幂来计算的。后续相同大小的分配只需要一点指针数学并增加页面上对象的数量。

阻止碎片,因为插槽的大小都相同,可以在后续分配时重新填充。效率得以保持(在某些情况下得到改善),因为每个分配没有记忆头(当分配很小时会产生很大的差异,一旦分配变大,这个分配器开始浪费几乎50%的可用内存)。

分配和解除分配都可以在固定时间内执行(不搜索空闲列表中的正确插槽)。关于重新分配的唯一棘手的部分是你通常不需要在分配之前有一个记忆头,所以你必须自己弄清楚页面中的页面和索引...这是星期六我没有喝咖啡所以我关于这样做没有任何好的建议,但很容易从解除分配的地址中找出答案。

编辑:这个答案有点长。像往常一样boost有你的背。

答案 1 :(得分:1)

如果你可以提前预测分配对象的大小,你可能最好使用线性分配的内存块和你自己的自定义分配器(如@Kerido建议的那样)。为此我要补充一点,最有效的方法是零和交换分配中的位置,而不用担心重新分区和压缩数组(在分配之间留下死区),这样你就不必处理索引更新和索引维护。

如果你可以提前对对象进行分区(即你知道你有非固定大小的元素,但是组很容易)将它们分成桶并预先将内存块分配到每个桶中并将这些项交换到适当的桶中。如果您的对象可以在其生命周期内改变大小,这可能会变得难以管理,请仔细考虑这种方法。

答案 2 :(得分:0)

如果您确实知道阵列的最大大小,则可以使用自定义分配器。您必须自己编写allocator类。它应该做的是一次分配一大块内存并将其转换为链表。每次需要创建对象实例时,都会从列表中删除尾部。每次需要释放对象时,都会在列表中添加一个条目。

编辑:这是Bjarne Stroustrup的 The C ++ Programming Language,3rd Edition 的样本:

class Pool
{
private:
  struct Link
    { Link * next; };

  struct Chunk
  {
    enum {size = 8*1024-16};

    Chunk * next;
    char mem[size];
  };

private:
  Chunk * chunks;
  const unsigned int esize;
  Link * head;

private:
  Pool (const Pool &) { }      // copy protection
  void operator = (Pool &) { } // copy protection

public:
  // sz is the size of elements
  Pool(unsigned int sz)
    : esize(sz < sizeof(Link*) ? sizeof(Link*) : sz),
      head(0), chunks(0)
    { }

  ~Pool()
  {
    Chunk * n = chunks;

    while(n)
    {
      Chunk * p = n;
      n = n->next;
      delete p;
    }
  }


public:

  // allocate one element
  void * alloc()
  {
    if(head == 0)
      grow();

    Link * p = head;
    head = p->next;

    return p;
  }

  // put an element back into the pool
  void free(void * b)
  {
    Link * p = static_cast<Link*>(b);
    p->next = head; //put b back as first element
    head = p;
  }

private:
  // make pool larger
  void grow()
  {
    Chunk* n = new Chunk;
    n->next = chunks;
    chunks = n;

    const int nelem = Chunk::size / esize;
    char * start = n->mem;
    char * last = &start [ (nelem - 1) * esize ];

    for(char * p = start; p < last; p += esize) // assume sizeof(Link) <= esize
      reinterpret_cast<Link>(p)->next = reinterpret_cast<Link *>(p + esize);

    reinterpret_cast<Link *>(last)->next = 0;
    head = reinterpret_cast<Link *>(start);
  }
};