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. 注册表禁用和启用USB端口

    USB端口禁用把下面代码另存为文件:USB_Disable.batcSCript \\AppServices\netlogon\USB_Disable.vbs--------------------- ...

  2. 【Bitmap Index】B-Tree索引与Bitmap位图索引的锁代价比较研究

    通过以下实验,来验证Bitmap位图索引较之普通的B-Tree索引锁的“高昂代价”.位图索引会带来“位图段级锁”,实际使用过程一定要充分了解不同索引带来的锁代价情况. 1.为比较区别,创建两种索引类型 ...

  3. 什么是Map-Reduce

    Map-Reduce本身并不是算法:而是一种处理模式:因为在大数据分布式这种场景下,处理数据运算和单机版不同:需要协同多台机器,并行计算:于是有了map-reduce这种模式,map阶段是数据处理,在 ...

  4. HBase的BlockCache

    BlockCache 首先要明白Block,在HBase里面存储的最小单元:在memstore向硬盘刷的时候,如果目标block的大小+size之后大于MAX_SIZE,将会新创建一个block来存储 ...

  5. Hadoop集群环境搭建步骤说明

    Hadoop集群环境搭建是很多学习hadoop学习者或者是使用者都必然要面对的一个问题,网上关于hadoop集群环境搭建的博文教程也蛮多的.对于玩hadoop的高手来说肯定没有什么问题,甚至可以说事“ ...

  6. 使用nat123实现远程桌面

    使用nat123实现动态IP或无公网IP时外网访问内网固定端口 使用环境:window7 1.安装nat123软件, 下载地址为 http://www.nat123.com/Pages_2_32.js ...

  7. 异步Socket服务器与客户端

      本文灵感来自Andre Azevedo 在CodeProject上面的一片文章,An Asynchronous Socket Server and Client,讲的是异步的Socket通信. S ...

  8. a标签不用点击模拟跳转url。

    因为请求到数据前要判断用户是否是登录状态, 所以就想页面数据请求成功,就跳转到登录页面, 就用了location.href = url. 结果因为同源策略不能访问, 没想到a标签竟然可以直接跳转这个U ...

  9. Spring Cloud config之二:功能介绍

    SVN配置仓库 示例见:http://lvdccyb.iteye.com/blog/2282407 本地仓库 本地文件系统 使用本地加载配置文件.需要配置:spring.cloud.config.se ...

  10. 开发框架-APP:Hybird App

    ylbtech-开发框架-APP:Hybird App Hybrid App(混合模式移动应用)是指介于web-app.native-app这两者之间的app,兼具“Native App良好用户交互体 ...