C++内存管理(上) 池分配器、对齐

  内存对程序效率的影响很大。即使是好的算法,用了不正确的内存分配,仍然会有效率问题。

内存对效能的影响有两方面:

  1. 动态内存分配(dynamic memory allocation)非常慢。它慢主要有两个原因。首先,堆分配器必须处理任何大小的分配请求;其次,在多数操作系统上,malloc()/free()必然会从用户模式(user mode)切换至(kernel mode),处理请求,再切换回来,这种上下文切换(context-switch)会非常耗时。
  2. 软件访问效能受其内存访问模式(memory access patter)主宰。数据置于连续内存块,比起置于广阔内存地址要更高效(cache等原因)。

      因此,游戏开发中一个常见的经验法则是:维持最低限度的堆分配,并且永不在紧凑循环中使用堆分配。

      我们可以不使用操作系统提供的分配器,而根据具体情况,使用自定义的分配器。多数游戏引擎会实现一个或多个定制分配器(custom allocator)。他比操作系统分配器更优的原因有二:

  3. 定制分配器从预分配的内存中完成分配请求。这样就不需要进行上下文切换。

  4. 通过对定制分配器的使用模式做出多个假设,定制分配器便可以比通用的堆分配器高效。

几种常见的定制分配器:

池分配器(pool allocator)

  主要用于分配内存大小固定的情况。
  池分配器的工作方式如下。首先,池分配器会预分配一大块内存,其大小刚好是分配元素的倍数。池内每个元素会加到一个存放自由元素的链表。池分配器收到分配请求时,就会把自由链表的下一个元素取出,并传回该元素。释放元素之时,只需简单地把元素插回自由链表中。

  注意!储存自由元素的链表可以实现为单链。但是这样就要花费额外空间来存储链表的指针。意识到,自由列表内的内存块,按定义来说就是可用的内存。那为什么不用这些内存本身来储存自由列表的“next”指针呢?只要元素尺寸≧sizeof(void*),就可以使用这个小诀窍了。

  若元素尺寸小余指针,则可以使用池元素的索引代替指针去实现链表。例如元素大小为16位,那么池里元素个数不超过2^16即可。(因为自由列表的内存是连续的,用索引可以算出其地址)。

具体实现

先看头文件的申明。

// freelist.h
class Freelist
{
public:
    Freelist* m_next;
};

class FreelistHead: private Freelist {
public:
    FreelistHead(int num, size_t elementSize);
    ~FreelistHead();
    inline void* Obtain(void);
    inline void Return(void* ptr);
private:
    void * m_start;
};

  Freelist类是链表的每个结点。FreelistHead类是链表的头结点,也代表了内存分配器。

测试代码如下:

//main.cpp
int main() {
    FreelistHead freelist(2, 64); // elementsize >= sizeof(void*)

    void* object0 = freelist.Obtain();
    cout<<object0<<endl;

    void* object1 = freelist.Obtain();
    cout<<object1<<endl;

    // obtained slots can be returned in any order
    freelist.Return(object1);
    freelist.Return(object0);
    return 0;
}

类成员函数的具体实现如下:

//freelist.cpp
FreelistHead::FreelistHead(int num, size_t elementSize) {

    union element {
        void* as_void;
        char* as_char;
        Freelist* as_self;
    }start, end, now, nxt;

    unsigned long step = elementSize/8;
    start.as_void = malloc(num*elementSize);
    end.as_char = start.as_char + (num*step);
    m_start = start.as_void;

    // initialize the free list - make every m_next of each element point to the next element in the list
    m_next = start.as_self;

    for (now=start, nxt.as_char = start.as_char + step;
         nxt.as_char < end.as_char;
         now=nxt, nxt.as_char = nxt.as_char + step) {
        now.as_self->m_next = nxt.as_self;
    }

    now.as_self->m_next 
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值