高流通量、小分配的高效堆管理器?
我正在寻找一种堆管理器来处理特定情况:许多非常小的分配,每个分配范围从12到64字节不等。任何更大的分配,我将传递给常规堆管理器,因此只需要处理微小的块。只需要4字节对齐。
我的主要关注点是
- 开销。常规libc堆通常会将分配舍入为16字节的倍数,然后再添加16字节的头 - 这意味着对于20字节的分配,超过50%的开销,这很糟糕。
- 性能
一个有用的方面是,Lua(这是这个堆的用户)将告诉您在调用free()时它正在释放的块的大小 - 这可能启用某些优化。
我将发布我的当前方法,它可以正常工作,但如果可能的话,我想对其进行改进。任何想法?
原文链接 https://stackoverflow.com/questions/228081
答案可能取决于这些对象的生存期模式。如果对象在您进行操作时全部实例化,然后一次性全部移除,那么创建一个非常简单的堆管理器可能是有意义的,它通过简单地增加指针来分配内存。然后,当您完成后,消除整个堆。
雷蒙德·陈(Raymond Chen)发布了一篇 有趣的帖子,这可能会给您带来灵感。:)
如果一堆内存在继续下一轮分配之前被分配、使用、释放了,建议使用最简单的分配器:
typedef struct _allocator {
void* buffer;
int start;
int max;
} allocator;
void init_allocator(size_t size, allocator* alloc) {
alloc->buffer = malloc(size);
alloc->start = 0;
alloc->max = size;
}
void* allocator_malloc(allocator* alloc, size_t amount) {
if (alloc->max - alloc->start < 0) return NULL;
void* mem = alloc->buffer + alloc->start;
alloc->start += bytes;
return mem;
}
void allocator_free(allocator* alloc) {
alloc->start = 0;
}
为了补充Greg Hewgill的观点,一种实现极其高效的固定大小堆的方法如下:
1.将一个大缓存拆分为节点。节点大小至少为sizeof(void*)。 2.使用每个空闲节点的前sizeof(void*)字节作为链接指针,将它们连接成一个单向链表(“空闲列表”)。分配的节点将不需要链接指针,因此每个节点的开销为0。 3.通过移除列表的头并返回它来分配节点(2个加载,1个存储)。 4.通过插入到列表的头部来释放节点(1个加载,2个存储)。
显然,第3步还必须检查列表是否为空,如果为空,则需要执行一些获取新大缓存(或失败)的工作。
正如Greg D和hazzen所说的那样,更高效的方法是通过增加或减少指针(1个加载,1个存储)来分配,而根本不提供释放单个节点的方式。
编辑:在两种情况下,free都可以处理“我通过常规堆管理器传递的任何更大的东西”的复杂情况,因为在调用free时会返回大小。否则,您可能会看到要么是标志(每个节点的开销可能为4个字节),要么是在您使用的缓冲区的某种记录中进行查找。
我使用一个基本上是O(1)的小块内存管理器(SBMM)。基本上它的工作方式如下:
它从操作系统中分配更大的SuperBlocks并将其跟踪起始和结束地址作为一个范围。SuperBlock的大小是可以调整的,但1MB是一个非常好的大小。
SuperBlocks被分为块(大小也可以调整...4K-64K是好的,根据您的应用程序)。每个块处理特定大小的分配并将所有项存储在块中作为其单链表。当您分配一个SuperBlock时,您会创建一个自由块的链表。
分配一个项目的意思是A)检查是否有处理该大小的空闲项的块 - 如果没有,则从SuperBlocks分配一个新的块。B)从块的自由列表中删除该项。
根据地址释放一个项目的意思是A)查找包含地址的SuperBlock(*) B)在SuperBlock中找到Block(减去SuperBlock起始地址并除以块大小) C)将项目推回到块的自由项列表中。
正如我所说,这个SBMM非常快,因为它具有O(1)的性能(*)。在我实现的版本中,我使用了一个AtomicSList(类似于Windows中的SLIST),因此它不仅具有O(1)的性能,而且在实现中是ThreadSafe和LockFree的。如果您想,实际上可以使用Win32 SLIST实现算法。
有趣的是,从SuperBlocks分配块或从块分配项目的算法的代码几乎相同(它们都是从Free List中进行O(1)分配)。
(*) SuperBlocks是按O(1)的平均性能排列在范围映射中(但在最坏情况下可能为O(Lg N),其中N是SuperBlocks的数量)。范围映射的宽度取决于大致知道您需要多少内存才能获得O(1)的性能。如果您超调,您会浪费一些内存,但仍会获得O(1)的性能。如果您保守估计,您将接近O(Lg N)的性能,但N是针对SuperBlock计数 - 而不是项计数。由于SuperBlock计数与项计数相比非常低(在我的代码中大约有20个二进制数量级),因此它不像分配器的其他部分那样关键。
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
- 如何编写 Lua 模式将字符串(嵌套数组)转换为真正的数组?
- 如何创建一个 lua 脚本以针对特定键为 fluentbit 进行限流
在对象大小都相同的情况下,可以构建非常高效的堆管理器。您可以为每个所需的对象大小创建一个这样的堆,或者如果您不介意使用一些空间,可以为 16 字节、32 字节和 64 字节的对象分别创建一个堆。最大的开销将是 33 字节的分配的 31 字节开销(将使用 64 块大小的堆)。