quake3引擎之内存池(二)

low alloc free High alloc

由于工作比较忙,很久没上来更新了。突然一看快一年了,整理也做了不少东西。就继续接着说吧。
因为水平有限,讲的不对的地方,欢迎指正。
上次最后谈到的是quake3游戏引擎的内存池部分。现在的编译器和操作系统对于动态内存的分配其实已经做的很优化了,令人难以忍受的内存碎片出现的可能性其实是很小了,甚至一些操作系统的代码在分配内存的时候都直接采用的malloc这种系统函数来分配。这里只是学习卡马克大神的思想,内存总有限制,思想却是无疆的。
除了quake3引擎之内存池(一)中提到的zone内存池之外,还包含了另外一种hunk的内存池。具体在引擎中的使用我没有完全看透彻,大致用于文件系统的读取,另外包含一些临时内存的分配等等。
Hunk内存池主要有两种分配方式:一种是Hunk_Alloc的方式,用于分配可能一直运行到内存池释放才释放的内存,另外一种就是Hunk_TempAlloc的方式,用于分配临时用完即刻释放的内存。
zone内存块在分配的时候轮询查找空闲并且大小满足的内存块,然后分配之后,如果该空闲内存块剩余的大小大于阀值,则分割为另外一个空闲内存块,释放的时候,合并上一个或者下一个空闲内存块为一个内存块,保证不存在两个连续空闲内存块的方式(这样比固定大小块,或者阶梯式大小的内存池在内存利用率上,理论上讲是优化不少的)。
而Hunk内存池的分配有别于以上方式,采用的是两端向中间挤的方式

low alloc free High alloc
hunk_block hunk_head ...

直接上干货。
内存池结构如下:

hunk_block hunk_head ...

hunk_block和hunk_head不一定是连续分布的,上面只是说明,内存池中主要分配这两种结构不同的内存块。
其中HunkBlock结构体如下

/* block 内存块,用于Hunk_Alloc函数分派的内存 */
typedef struct HunkBlock_s {
    int     size;    /* 用于记录当前block的大小 */
    qbyte   printed;  /* 标记是否打印过 */
    struct HunkBlock_s  *next;  /* 指向下一个block */
    char    *label;  /* 分配的内存大小(按字符串显示,主要用于打印内存使用状况) */
    char    *file;  /* 分配的文件 */
    int     line;  /* 行号 */
} HunkBlock_t;

HunkHead结构体如下

/* hunk内存块,用于Hunk_Temp函数分派的内存 */
typedef struct {
    int     magic;
    int     size;
} HunkHead_t;

内存实际分配在一个byte类型的全局变量中

static qbyte    *s_hunkData = 0;  /* 内存池 */
static int      s_totalhunk;  /* 内存池大小 */

用于保存block头的全局变量

static HunkBlock_t  *hunkblocks;  /* 该变量永远指向最后分配的Hunk_block内存块,用于打印输出当前分配信息等 */

用于记录高低端(记得上面说过的Hunk是从两端往中间分配的方式)内存分配情况的HunkUsed结构

typedef struct {
    int     mark;
    int     permanent;
    int     temp;
    int     tempHighwater;
} HunkUsed_t;
static QHunkUsed_t  hunk_low, hunk_high;  /* 记录两端使用的情况 */
static QHunkUsed_t  *hunk_permanent, *hunk_temp;  /* 指向分派内存的两端,根据条件不停的调整指向 */

Hunk_Alloc分配Block内存的时候使用hunk_permanent指向的端,HunkTempAlloc分配的时候使用hunk_temp指向的端。每次分配之前都会检测当前分派内存情况,然后交换指向

static void HunkSwapBanks(void) {
    QHunkUsed_t *swap;

    /* can't swap banks if there is any temp already allocated */
    if (hunk_temp->temp != hunk_temp->permanent) {
        return;
    }

    /* if we have a large highwater mark on this side, start marking 
       our permanent allocations here and use the other side for temp
     */
    if (hunk_temp->tempHighwater - hunk_temp->permanent > hunk_permanent->tempHighwater - hunk_permanent->permanent) {
        swap = hunk_temp;
        hunk_temp = hunk_permanent;
        hunk_permanent = swap;
    }
}

接着是两个分派内存的函数,没有什么特殊的操作

/* 用于在合适的端分配Block类型(不释放)的内存块 */
void *Q_HunkAlloc(int size, QHAPRef preference) {
#endif
    void *buf;

    if (s_hunkData == 0) {
        Com_Log(Q_FATAL, "Q_HunkAlloc: Hunk memory system not initialized");
    }

    /* can't do preference if there is any temp allocated */
    if (preference == Q_HUNK_DONTCARE || hunk_temp->temp != hunk_temp->permanent) {
        Q_HunkSwapBanks();
    } else {
        if (preference == Q_HUNK_LOW && hunk_permanent != &hunk_low) {
            Q_HunkSwapBanks();
        } else if (preference == Q_HUNK_HIGH && hunk_permanent != &hunk_high) {
            Q_HunkSwapBanks();
        }
    }

#ifdef QDEBUG
    size += sizeof(QHunkBlock_t);
#endif

    /* round to cache line */
    size = (size + 31) & ~31;

    if (hunk_low.temp + hunk_high.temp + size > s_totalhunk) {
#ifdef QDEBUG
        Q_HunkLog();
        Q_HunkSmallLog();
#endif
        Com_Log(Q_ERROR, "Q_HunkAlloc failed on %i\n", size);
    }

    if (hunk_permanent == &hunk_low) {
        buf = (void *)(s_hunkData + hunk_permanent->permanent);
        hunk_permanent->permanent += size;
    } else {
        hunk_permanent->permanent += size;
        buf = (void *)(s_hunkData + s_totalhunk - hunk_permanent->permanent);
    }

    hunk_permanent->temp = hunk_permanent->permanent;

    memset(buf, 0x0, size);

#ifdef QDEBUG
    {
        QHunkBlock_t *block;
        
        block = (QHunkBlock_t *)buf;
        block->size = size - sizeof(QHunkBlock_t);
        block->file = file;
        block->label = label;
        block->line = line;
        block->next = hunkblocks;
        hunkblocks = block;
        buf = ((qbyte *)buf) + sizeof(QHunkBlock_t);
    }
#endif
    return buf;
}

/* 用于在合适的端分派Head内存块 */
void *Q_HunkAllocateTempMemory(int size) {
    void *buf;
    QHunkHead_t *hdr;

    /* return a Q_ZAlloc'd block if the hunk has not been initialized */
    if (s_hunkData == 0) {
        return Q_ZMalloc(size);
    }

    Q_HunkSwapBanks();

    size = ((size + 3) & ~3) + sizeof(QHunkHead_t);

    if (hunk_temp->temp + hunk_permanent->permanent + size > s_totalhunk) {
        Com_Log(Q_FATAL, "Q_HunkAllocateTempMemory: failed on %i\n", size);
    }

    if (hunk_temp == &hunk_low) {
        buf = (void *)(s_hunkData + hunk_temp->temp);
        hunk_temp->temp += size;
    } else {
        hunk_temp->temp += size;
        buf = (void *)(s_hunkData + s_totalhunk - hunk_temp->temp);
    }

    if (hunk_temp->temp > hunk_temp->tempHighwater) {
        hunk_temp->tempHighwater = hunk_temp->temp;
    }

    hdr = (QHunkHead_t *)buf;
    buf = (void *)(hdr + 1);

    hdr->magic = Q_HUNK_MAGIC;
    hdr->size = size;

    /* don't bother clearing, because we are going to load a file over it */
    return buf;
}

其中需要注意的是,采用Temp分配的内存,在释放前,如果中间出现其他的Hunk内存的分配的话,可能会导致指针指向发生变化,因而在释放的时候会引发内存池分派异常。
文件部分的源码未仔细阅读,暂时还不清楚这个Hunk内存池的主要作用。

你可能感兴趣的:(quake3引擎之内存池(二))