如何在macOS上用汇编重新实现高性能的realloc?
如何实现高性能realloc(达到系统库无拷贝扩容的效果)
我做这个纯粹是出于兴趣,并非实际需求。之前我在Linux上用mmap和mremap实现过realloc。用dtruss运行下方代码后,只看到两个write调用,中间无其他操作。通过bash的time命令测试发现b.c的运行速度明显慢于a.c,这说明系统库的realloc并没有用memcpy,而是通过修改虚拟页实现的扩容。如果我想自己实现C库或者用汇编写realloc,该怎么操作才能达到和a.c一样的高性能?
// a.c #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { write(1, "Hel", 3); void*p1=malloc(1024*1024*512); memset(p1, '1', 1024*1024*512); malloc(4096); void*p =realloc(p1, 1024*1024*1024); memset(p+1024*1024*512, '0', 1024*1024*512); write(1, "lo\n", 3); } // b.c #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { write(1, "Hel", 3); void*p1=malloc(1024*1024*512); memset(p1, '1', 1024*1024*512); void*p =malloc(1024*1024*1024); memcpy(p, p1, 1024*1024*512); memset(p+1024*1024*512, '0', 1024*1024*512); write(1, "lo\n", 3); }
实现思路与关键步骤
1. 核心依赖:Linux的mremap系统调用
系统库realloc无拷贝扩容的本质是用了mremap——这个调用可以直接调整虚拟内存区域的大小和位置,只要原内存块后续的虚拟地址空间未被占用(就像你代码里malloc(4096)后仍能扩容,说明那4k内存没挡住大内存块的后续虚拟空间)。自己实现时:
- 设定一个内存阈值(比如128KB),超过阈值的大内存块直接用
mmap分配,并记录该块的元数据(大小、是否为mmap分配等)。 - 扩容时先检查原块后续虚拟地址是否空闲,若空闲则直接调用
mremap调整大小,全程无需用户态拷贝。
2. 内存元数据管理
要高效处理realloc,必须维护每个内存块的元数据:
- 可以在每个内存块头部嵌入元数据,或者用哈希表/链表单独存储:包含分配大小、实际占用的虚拟页范围、是否为mmap分配的标记、相邻块的空闲状态。
- 处理realloc请求时,先查元数据:如果是mmap分配的大块,优先尝试
mremap扩容;如果是堆上的小块,先看相邻空闲块能否合并,不行再走拷贝逻辑。
3. 无拷贝扩容的条件判断
- 对于mmap分配的内存:调用
mremap时带上MREMAP_MAYMOVE标志,若后续空间不足,内核会自动把整个内存块迁移到其他空闲虚拟地址区域,这个过程内核负责调整页表,用户态无需拷贝数据。 - 对于堆上的小块内存:维护空闲块链表,扩容时检查当前块的下一个块是否空闲且大小足够,若满足则合并两个块,调整元数据即可,无需拷贝。
4. 汇编优化(可选)
如果用汇编实现,重点优化这几点:
- 元数据快速查找:用寄存器传递元数据指针,减少内存访问开销。
- 直接调用系统调用:x86_64架构下直接用
syscall指令调用mremap,跳过C库封装的额外开销。 - 空闲块合并逻辑:用汇编实现链表的快速遍历与合并,降低分支预测失败的概率。
5. 边界处理
- 小内存块:小于mmap阈值的内存块,还是用传统堆分配(比如基于链表的空闲块管理),兼顾小内存分配效率与大内存扩容性能。
- fallback逻辑:当
mremap失败(比如无足够虚拟内存),退回到malloc+memcpy+free的传统流程,保证功能可用性。
内容的提问来源于stack exchange,提问作者Stan
相关产品推荐
相关产品推荐

