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内存池的主要作用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容

  • 嵌入式系统的内存管理 姓名:张猛 引用自:http://blog.csdn.net/baskmmu/article...
    oliverabc阅读 2,084评论 0 0
  • 11.看下面的程序,第一个NSLog会输出什么?这时str的retainCount是多少?第二个和第三个呢? 为什...
    AlanGe阅读 726评论 1 4
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,139评论 30 470
  • 今天收到关于app Store 驳回的提交版本,是关于定位信息的,最近,苹果对用户权限审核越来越严格,所以之前好多...
    兜兜Jerry阅读 975评论 0 1
  • 初级的快乐是肉体的快乐,那是饱、暖、物、欲。 中级的快乐是精神的快乐,那是诗词歌赋、琴棋书画、游走天下。 高级的快...
    献礼1127阅读 216评论 0 0