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. 使用jsonp进行跨站点数据访问

    使用jsonp 可以解决ajax 的跨域问题,使用起来比较简单. 具体的测试环境搭建如下 1.一个简单的MVC 站点 2.一个简单的html文件(这次的测试是在web 站点中,当然可以是一个简单的ht ...

  2. nyoj 单调递增子序列(二)

    单调递增子序列(二) 时间限制:1000 ms  |  内存限制:65535 KB 难度:4   描述 给定一整型数列{a1,a2...,an}(0<n<=100000),找出单调递增最长 ...

  3. Socket-Vs-WebSocket-TestTool

    项目地址 :  https://github.com/kelin-xycs/Socket-Vs-WebSocket-TestTool Socket-Vs-WebSocket-TestTool 一个用 ...

  4. tomcat源码阅读之生命周期(LifeCycle)

    一.事件机制流程: 1.     当外部事件源发生事件(比如点击了按钮,数据发生改变更新等)时,事件源将事件封装成事件对象Event: 2.     将事件对象交由对应的事件派发器Dispatcher ...

  5. zstack快速安装文档

    1.环境准备 1.1 准备软件工具 系统镜像 ZStack-x86_64-DVD-2.1.1.514.iso Zstack安装包 ZStack-installer-2.1.1.514.bin http ...

  6. oracle之 手动创建 emp 表 与 dept 表

    说明: 有时候我们需要通用的实验数据,emp表 与 dept表  但是数据库中有没有. 这时,我们可以手动创建. -- 创建表与数据CREATE TABLE EMP(EMPNO NUMBER(4) N ...

  7. Map 的营救;使对象属性有顺序

    使用ES6的新特性Map.Map 对象以插入的顺序遍历元素.for...of循环为每一次循环返回一个[key, value]数组. 如果想在跨浏览器环境中模拟一个有序的关联数组,你要么使用两个分开的数 ...

  8. 一个最简的Thinkphp3.2 操作Mongodb的例子

    看到Thinkphp网站上没有调用Mongodb的例子,就写一个一个最简的Thinkphp操作Mongodb的例子.欢迎讨论[前提]Thinkphp对Mongdb的支持依赖于PHP对Mongodb的支 ...

  9. jquery.validate.js remote (php)

    网上的人不厚道呀 validate 这玩意的异步是 返回的 echo 'true'  或者 echo 'false';很少有人说呀~.~  转载了一篇原文: jquery.validate.js对于数 ...

  10. C++中reinterpret_cast、const_cast、static_cast、dynamic_cast的作用与区别

    1.reinterpret_cast 作用及原理:将一个类型的指针,转换为另一个类型的指针,这种转换不用修改指针变量值数据存放格式(不改变指针变量值),只需在编译时重新解释指针的类型就可以,当然他也可 ...