BlockAllocator 的内存管理情况可以用下图表示

  整体思路是,先分配一大块内存 Chunk,然后将 Chunk 分割成小块 Block。由于 Block 是链表的一个结点,所以可以通过链表的形式把未使用的 Block 连接起来,并保存到 pFreeLists 中。当我们向 BlockAllocator 申请一块内存时,BlockAllocator 会通过 pFreeLists 表索引出一块空闲的 Block,并返回其地址。当我们不断申请内存的时候,BlockAllocator 会不断返回未使用的 Block 块。当 Chunk 内存空间用尽时,BlockAllocator 会再开辟一块新的 Chunk。

  但是,分割 Chunk 时,Block 的大小是确定的。假如 Block 的大小为 148 字节,当只申请 64 字节大小的空间时,会造成浪费;当申请 256 字节时,Block 的大小不足;

  所以需要把 Block 根据大小分成不同的类型,如 64、148、256 ...,当申请内存的时候,BlockAllocator 先确定要申请内存的大小,再查找表,返回适当大小类型的 block。但是如何快速返回适当大小类型的 block?可以使用一个数组:

static int blockTypeSizeTable[BLOCK_TYPE_COUNT];

先通过下面的方式初始化 blockTypeSizeTable:

        /* 设置索引数组 blockTypeSizeLookup 的 block 类型索引值 */
if ( blockTypeSizeLookipInitialized == false ) {
blockTypeSizeLookipInitialized = true; int blockTypeSizeIndex = ;
for ( int i = ; i <= MAX_BLOCK_SIZE; i++ ) {
if ( i <= blockTypeSizeTable[blockTypeSizeIndex] ) {
blockTypeSizeLookup[i] = blockTypeSizeIndex;
}
else {
blockTypeSizeIndex++;
blockTypeSizeLookup[i] = blockTypeSizeIndex;
}
}
}

假设有三种类型大小的 Block,16 字节、32 字节、48字节,则数组 blockTypeSizeTable 的大小为 49, 初始化后的 blockTypeSizeTable 储存的内容是:

    0-16 储存索引 0

  17-32 储存索引 1

  33-48 储存索引 2

当我们申请大小为 36 字节的内存时,由于 36 落在区间 33-48 内,所以 blockTypeSizeTable[36] 会得到索引 2。然后通过 2 查找表 pFreeLists 即可获取大小为 48 字节的 Block。pFreeLists 是一个数组,储存所有类型 Block 链表的地址。pFreeLists[0] 指向的是大小为 16 字节的空闲 Block 链表,pFreeLists[2] 指向的是大小为 32 字节的空闲 Block 链表。

具体的分配操作在函数 allocate 中:

    void* BlockAllocator::allocate(int size)
{
if ( size == ) return ;
assert(size > ); /* 使用四个字节记录 block 的类型索引,free 是使用 */
size += sizeof(int); /* 申请的空间大于规定的最大值,直接申请,不放到块的链表中去 */
if ( size > MAX_BLOCK_SIZE ) {
int* data = ( int* ) malloc(size);
/* -1 表示这是直接分配的内存 */
data[] = UNKNOWN_MEMORY;
return (data + );
} int index = blockTypeSizeLookup[size];
assert( <= index && index < BLOCK_TYPE_COUNT); /* 存在同类型的未被使用的内存块?返回内存块 */
if ( pFreeLists[index] ) {
/* 使块头指针指向新的未被使用的 block */
Block* block = pFreeLists[index];
pFreeLists[index] = block->next;
return (( int* ) block + );
}
else {
/* 扩展 chunk 数组 */
if ( nChunkCount == nChunkSpace ) {
Chunk* oldChunks = pChunks;
nChunkSpace += CHUNK_ARRAY_INCREMENT;
pChunks = ( Chunk* ) malloc(nChunkSpace * sizeof(Chunk));
memcpy(pChunks, oldChunks, nChunkCount * sizeof(Chunk));
memset(pChunks + nChunkCount, , CHUNK_ARRAY_INCREMENT * sizeof(Chunk));
::free(oldChunks);
}
int chunkSize = chunkSizeTable[index];
/* 获取一个未被使用的可以用来分配内存的 chunk */
Chunk* chunk = pChunks + nChunkCount;
chunk->blocks = ( Block* ) malloc(chunkSize); /* 获取当前申请的 block 类型大小 */
int blockSize = blockTypeSizeTable[index]; /* 计算一块 chunk 内存能够分割成 block 的数量 */
int blockCount = chunkSize / blockSize;
assert(blockCount * blockSize <= chunkSize); /* 将 chunk 分割出许多 block,再将 block 以链表的形式串起来 */
for ( int i = ; i < blockCount - ; i++ ) {
Block* block = ( Block* ) (( char* ) chunk->blocks + blockSize * i);
Block* next = ( Block* ) (( char* ) chunk->blocks + blockSize * (i + )); block->sizeIndex = index;
block->next = next;
}
/* 将最后一个 block 的 next 指向空结点,表示这是最后一个 block */
Block* lastBlock = ( Block* ) (( char* ) chunk->blocks + blockSize * (blockCount - ));
lastBlock->sizeIndex = index;
lastBlock->next = nullptr; /* 将刚申请的 block 链表的第二块 block 保存到 pFreeLists 对应类型的数组中 */
pFreeLists[index] = chunk->blocks->next;
nChunkCount++; /* 返回刚申请的 block 链表的第一块 block */
return (( int* ) chunk->blocks + );
}
}

根据申请内存的大小获取 Block 类型的索引 index,然后通过 index 查找表 pFreeLists:

  1、存在未使用 Block,返回 Block,并使 pFreeLists 指向下一个未使用的 Block。

  2、不存在未使用 Block,申请一块 Chunk,分割 Chunk 为 Blocks,返回首 Block,并使 pFreeLists 指向下一个未使用的 Block。

值得注意的是:Block 内存的前四个字节是用来储存 Block 类型的信息 sizeIndex,所以在返回 Block 内存地址的时候,向后偏移了 4 个字节。通过申请内存大小索引 Block 类型时已经将 size 多添加了 sizeof(int) 个字节的大小。

当使用完 Block 后并返还给 BlockAllocator 时,需要知道当前 Block 的类型才能正确添加 Block 到对应类型的未使用链表中,所以前面要用四个字节的大小储存器类型信息 sizeIndex:

    void BlockAllocator::free(void* ptr)
{
int* data = ( int* ) ptr - ;
int index = data[]; if ( index == UNKNOWN_MEMORY ) {
::free(data);
return;
} /* 根据内存大小获取 block 类型的索引值,并判断是否有效 */
assert( <= index && index < BLOCK_TYPE_COUNT);
int size = blockTypeSizeTable[index]; /* 用头插法将 block 插到 pFreeLists[index] 指向的 block 链表中去 */
Block* block = ( Block* ) data;
block->next = pFreeLists[index];
pFreeLists[index] = block;
}

通过返回的地址向前偏移四个字节,获取 Block 类型信息,然后插入到未使用 Block 链表中。

当申请的内存过大时,BlockAllocator 会直接使用 malloc 函数分配内存(没有合适大小的 Block),并标记为 UNKNOWN_MEMERY。所以在释放时会调用 free 函数释放。

通过 BlockAllocator 可以实现顶点数据的内存管理。

基于OpenGL编写一个简易的2D渲染框架-12 重构渲染器-BlockAllocator的更多相关文章

  1. 基于OpenGL编写一个简易的2D渲染框架-05 渲染文本

    阅读文章前需要了解的知识:文本渲染 https://learnopengl-cn.github.io/06%20In%20Practice/02%20Text%20Rendering/ 简要步骤: 获 ...

  2. 基于OpenGL编写一个简易的2D渲染框架-06 编写一个粒子系统

    在这篇文章中,我将详细说明如何编写一个简易的粒子系统. 粒子系统可以模拟许多效果,下图便是这次的粒子系统的显示效果.为了方便演示,就弄成了一个动图. 图中,同时显示了 7 种不同粒子效果,看上去效果挺 ...

  3. 基于OpenGL编写一个简易的2D渲染框架-01 创建窗口

    最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的 ...

  4. 基于OpenGL编写一个简易的2D渲染框架-08 重构渲染器-整体架构

    事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求. 当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式.所以同时渲染粒 ...

  5. 基于OpenGL编写一个简易的2D渲染框架-04 绘制图片

    阅读文章前需要了解的知识,纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/ 过程简述:利用 FreeI ...

  6. 基于OpenGL编写一个简易的2D渲染框架-09 重构渲染器-Shader

    Shader 只是进行一些简单的封装,主要功能: 1.编译着色程序 2.绑定 Uniform 数据 3.根据着色程序的顶点属性传递顶点数据到 GPU 着色程序的编译 GLuint Shader::cr ...

  7. 基于OpenGL编写一个简易的2D渲染框架-03 渲染基本几何图形

    阅读文章前需要了解的知识,你好,三角形:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ 要 ...

  8. 基于OpenGL编写一个简易的2D渲染框架-02 搭建OpenGL环境

    由于没有使用GLFW库,接下来得费一番功夫. 阅读这篇文章前请看一下这个网页:https://learnopengl-cn.github.io/01%20Getting%20started/02%20 ...

  9. 基于OpenGL编写一个简易的2D渲染框架-11 重构渲染器-Renderer

    假如要渲染一个纯色矩形在窗口上,应该怎么做? 先确定顶点的格式,一个顶点应该包含位置信息 vec3 以及颜色信息 vec4,所以顶点的结构体定义可以这样: struct Vertex { Vec3 p ...

  10. 基于OpenGL编写一个简易的2D渲染框架-10 重构渲染器-Pass

    Pass,渲染通路,一个渲染通路指的是一次像素处理和一次顶点处理,也就是指的是一次绘制.简单来说就是顶点数据在渲染管线中走一遍最后绘制. 渲染粒子系统的粒子时,需要开启 OpenGL 的混合模式,并使 ...

随机推荐

  1. SQL语言:DDL/DML/DQL/DCL

    SQL (Structure Query Language)语言是数据库的核心语言. SQL 的发展是从1974年开始的,其发展过程如下: 1974年-----由Boyce和Chamberlin提出, ...

  2. es6比es5节省代码的地方总结

    对象方法简写: es5写法: var obj = { name: 'jeff', getName: function () { return this.name; } } es6写法(方法定义里,少写 ...

  3. Angular 4 投影

    1.创建工程 ng new demo4 2. 创建子组件 ng g component child 3.子组件html定义 <div class="wrapper"> ...

  4. TroubleShoot: Enable Developer Mode in Windows 10 Insider Preview Build 10074

    There is a known issue in Windows 10 Insider Preview build 10074 (see here). Developers cannot enabl ...

  5. MySQL的Join使用

    在MySQL(以5.1为例)中,表连接的语法可以参见MySQL官方手册:MySQL官方手册-JOIN 在查询中,连接的语法类似 SELECT select_expr FROM table_refere ...

  6. TFS错误-TF249053

    前几天规划了下代码结构,改了很多东西后,台式机依然正常访问,但是笔记本一连接或者更改TFS相关资源就报错TF249053.报错点击后不影响正常使用,但是很郁闷.于是查了下资料如下. 错误原因: htt ...

  7. linux 信号处理 三 (信号集的使用)

    sigprocmask系统调用 使用条件: 1.有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数.这种情况是通过阻塞信号实现的. 2. ...

  8. busybox下的tftp client

    # tftp -p -l file host    # put local file to remote host # tftp -g -r file host    # get remote fil ...

  9. Linux内核深入研究之进程的线性地址空间-传统版

    引言: 了解Linux环境下,进程的地址空间划分,对于我们理解Linux应用程序有很大的帮助,否则会被New与Malloc之类的指针操作弄的晕头转向,本文基于Linux内核讲述了Linux/Unix线 ...

  10. MySQL MHA环境搭建

    MHA功能: 1,从故障的mysql保存二进制日志时间(binlog events);2,识别含有最新更新的slave:3,应用差异的中继日志(relay log)到其他的slave:4,应用从mas ...