内存分配器设计的演进
文章目录
- 栈内存空间是否够用
- 系统调用申请内存
- 最简单的内存分配器实现 -- bump allocator
- 可扩容的 Bump alloactor
- 通过free-list 管理的 allocator
- 通过size-buckets 维护多个free-list 的 allocator
- Cache friendly allocator
- 需要考虑更多问题的allocator
- 性能
- 易用性
本文希望描述一个基本的内存分配器的设计演进,包括基于ptmalloc 实现的glibc-malloc ,jemalloc 以及 tcmalloc 为什么会被设计成如今的样子(他们的基本形态都是非常接近的,无非tcmalloc 比 jemalloc和glibc-malloc 多了一些内存高效利用上的设计)
在没有内存分配器的时候,我们os系统最开始的时候拥有自己的栈空间,用来保存函数栈以及内部的变量,大家先抛开我们习惯用的malloc/free 以及 new/delete,来整体看看内存分配器的演进过程,本文更多的是一些设计上的思考。
栈内存空间是否够用
如下代码提就是一个栈空间的基本数据存储。
#include <stdio.h>int main() {int a = 10;int b = 20;int c = a + b;return 0;
}
在编译生成的汇编代码中,我们可以看到对于变量 a 和 b的处理,其所占用的内存大小已经被分配好。10这个数值被填充到了代表a的栈底指针减去4字节之后的地址-4%(rbp)
,同理20 被填充到了栈底指针减去 8字节之后的地址-8(%rbp)
。
可以看到,编译期间,变量a,b 以及最后的c 都已经明确了具体的存储空间的大小以及存储对应的虚拟地址。
.file "stack.c".text.globl main.type main, @function
main:
.LFB0:.cfi_startprocpushq %rbp # 入栈.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp # 添加栈底指针.cfi_def_cfa_register 6movl $10, -4(%rbp) # 变量amovl $20, -8(%rbp) # 变量bmovl -4(%rbp), %edxmovl -8(%rbp), %eaxaddl %edx, %eaxmovl %eax, -12(%rbp) # 变量cmovl $0, %eaxpopq %rbp # 出栈.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE0:.size main, .-main.ident "GCC: (GNU) 7.3.1 20180303 (Red Hat 7.3.1-5)".section .note.GNU-stack,"",@progbits
栈内存分配基本优劣如下:
优势:
- Automacitcally handled by the compiler,编译器会编译期间分配好对应的栈内存
- very fast allocation 拥有对应的cpu指令来专门处理栈内存的分配
- very fast cleanup 释放的时候也拥有对应的指令(函数栈弹栈)
劣势:
- fixed sizes,内存大小需要是已知的,内存大小有限,比如x86_64 系统下默认的栈空间只有10M(
ulimit -s
) - fixed lifetimes : 作用域仅仅被限制在了当前函数内部。比如,想要在一个函数内部创建一个链表并返回,是完全不可能的。
栈内存对于我们应用程序的使用场景来说显然还是不够,那有没有可以直接分配内存的系统调用?
系统调用申请内存
mmap : 设置内存protection(mmap的内存区域可以被当前进程的其他程序更改),频繁的中断 以及 会陷入内核;释放的时候unmap即可。
Mmap 解决了 栈方式分配内存的 fixed-sizes 的问题,用户可以不限制内存大小来分配内存。
- 整体非常慢,频繁得触发page fault 中断并使当前进程陷入内核。
- 浪费内存较为严重。因为申请内存的粒度只能是4K的page,在一些应用场景中对内存浪费较为严重(申请8byte的区域,创建一个链表节点,需要消耗4k的内存)。
Mmap 并不能作为构造一个高效可靠的内存分配器的雏形,接下来还需要做更多的工作来对内存进行管理。
那我们想像这样一个简单的内存分配器,就是先模拟栈空间进行内存分配,取一个定长的内存区域被用来存储数据,分配的过程可以根据用户的需求来分配。
最简单的内存分配器实现 – bump allocator
bump allocator,类似一个用户栈空间(不用 os 自己的进程栈空间)。
用户申请一个内存区域,则将申请的这个内存对象入栈。这个栈空间管理的内存能够控制变量的生存周期,只有当你从栈中移除这个内存对象的时候,这个内存对象的生命周期才算结束。
优势:
- 高性能,fast allocation
- 变量的存储生命周期可控
缺点:
- fixed total memory,需要开辟一段固定大小的内存。
- cannot free memory: 这个含义其实是说,因为使用的是栈空间管理的内存,想要释放最开始申请额8 bytes 的存储空间,需要先释放掉在 8之上的其他内存对象,这个时候其他的内存对象对于应用程序来说并不一定想要释放。
问题很明显,还是和传统的函数栈空间一样 fixed total memory 以及 无法快速释放内存。
可扩容的 Bump alloactor
那我们先来通过expandable memory 来解决 fixed total memory的问题。
当用户程序耗尽了第一次申请的内存区域,则分配一个新的 内存区域用于用户申请(内存管理的arena机制),已经满的内存区域则保留,直到用户想要从栈顶释放内存。
优势:
- Fast allocation
- expandable total memory
- manual lifetime
劣势:
- cannot free memory,仍然无法有效释放内存,需要先弹栈,才能找到需要释放的内存对象。
通过free-list 管理的 allocator
free-list,通过一些元数据来管理内存空间,比如使用链表,将应用程序申请的内存对象通过链表串联起来, 如果用户想要释放一个变量,则释放这个变量对应内存的单链表节点即可。
现在free memory终于可以随意进行了,能够随意申请,随意释放,不会受内存大小的限制,这样看起来功能确实没有问题了。
可是我们还需要性能 以及 成本,,,
劣势:
- allocations have minium sizes,一个链表指针在64位系统下需要8byte的存储空间,表示每次申请至少需要8byte的存储空间 管理free-list 的节点。
- very slow (释放的时候,可以随意释放任何一个节点,只是需要从头顺序扫描整个链表,代价很高)
- memory fragmentation 内存碎片,整个内存空间在大内存和小内存对象混合在一起的时候无法有效管理小内存。比如一个链表节点大小最小是8bytes,用户申请了一个1byte 和 一个 7bytes的存储空间,这个时候需要至少16bytes的存储,1byte 对应的存储区域会有一个7bytes的内存碎片一直无法被使用。
这样的性能 以及 对成本这样的浪费,简直不能忍,继续向下看。
通过size-buckets 维护多个free-list 的 allocator
每一个size(不太可能每一个size 一个free-list,不易维护) 或者 一批 size 维护一个自己的free-list,这样当前free-list 只需要存储大小接近的内存对象就可以了,内存碎片问题会被极大的降低。
且释放内存的时候不需要扫描整个混合在一起的大链表,扫描被均分到不同的size buckets。
优势:
- 在前面的基础上能够正常释放内存
- 解决了扫描慢的问题(释放时不需要扫描整个大链表,只需要扫描对应的size-buckets 的链表即可)
- 内存碎片问题(通过按照size 来划分free list,有效得降低了内存碎片)
劣势:
- Cache pressure ;在高并发下分配内存的过程中频繁的cache-miss 会导致cpu 的负载较高。cpu 需要频繁得在不同的size-buckets 下进行切换,无法保持良好的cache命中(每次遍历都需要重新load 内存到cpu-cache,链表的访问本身是随机访问,加上不同的size-buckets 的访问,都会导致cpu cache-miss较高)。
Cache friendly allocator
slab allocator,每一个size维护一个slab,这个 slab只需要管理当前size 的内存分配即可。
解决了cpu 对链表数据的随机访问的影响, 每一个slab 内部使用数组来管理内存对象,数组对cpu-cache 更友好。
每一个slab 能够管理一批连续的内存空间,内存在slab上的分布连续性较强。
这个连续性肯定是相比于链表的随机访问来说的,在slab上尽可能连续的分配当前size,释放的时候能够尽可能得降低cache miss,减少cpu-cache 的压力。
到此,我们有了一个功能齐全,性能还不错的内存分配器的概要设计,但这只是单线程场景的内存分配器。想要实现一个工业级的内存分配器,还有更多的问题需要去考虑。
需要考虑更多问题的allocator
性能
- Alignment。尽可能对齐cache-line,来保证cpu 访问的性能
- Multi-Thread / Multi-core 下的性能,比如jemalloc 和 tcmalloc 设计的 thread-cache / per-cpu cache等
易用性
- C compatibility – 兼容c语言的malloc/free等,想要支持 C++,这需要支持 operator new/delete
- Debugging,需要能够暴露内部状态,方便debug或者用户查看内存占用情况
- Binary size – 运行库文件不能过大,它只是一个内存分配库
- …
这也就是jemalloc/tcmalloc/glibc-malloc 为什么如此通用的原因,因为其实现过程是在功能/性能/成本之间的trade-off,且这个trade-off 达到了极致,被工业界认可。
相关文章:

Android OpenGL ES(十一)绘制一个20面体 .
前面介绍了OpenGL ES所有能够绘制的基本图形,点,线段和三角形。其它所有复杂的2D或3D图形都是由这些基本图形构成。 本例介绍如何使用三角形构造一个正20面体。一个正20面体,有12个顶点,20个面,30条边构成:…

Java项目:学生选课系统(java+javaweb+jdbc)
源码获取:博客首页 "资源" 里下载! 功能介绍: 用户菜单、学生管理、教师管理、课程管理、成绩排名查询 学生管理控制层: Controller RequestMapping("/student") public class StudentController {private …

Xtrabackup对mysql全备以及增量备份实施
Xtrabackup对mysql全备以及增量备份实施1.完全备份与恢复本文使用的是centos5.8 64位系统,mysql 使用5.5.35.如果要使用一个最小权限的用户进行备份,可基于以下:mysql> createuser bkuserlocalhost identified by redhat;mysql> grant …

js浅拷贝和深拷贝
浅度拷贝:复制一层对象的属性,并不包括对象里面的为引用类型的数据,当改变拷贝的对象里面的引用类型时,源对象也会改变。 深度拷贝:重新开辟一个内存空间,需要递归拷贝对象里的引用,直到子属性都…

关于 fallocate 文件系统预分配 的一些细粒度测试
文章目录Rocksdb 中的预分配Fallocate in rocksdb 性能测试Fallocate 使用 以及 对应配置的行为API 使用不同 Mode 的行为分配磁盘空间释放磁盘空间折叠/裁剪 文件内容清零文件 扩容文件Rocksdb 中的预分配 预分配文件存储空间 在存储引擎中用的还是比较频繁的,尤…

mac 使用nvm安装node
1.curl https://raw.github.com/creationix/nvm/master/install.sh | sh2。vi ~/.bash_profile 添加:source /Users/dujie/.nvm/nvm.sh nvm install 0.10.24 nvm use 0.10.24 # 默認使用 0.10.24 版本,否則每次關掉 Terminal 就得重新 nvm use 一次 $…

Java项目:人事管理系统(java+javaweb+jdbc)
源码获取:博客首页 "资源" 里下载! 功能介绍: 登录、新增、修改、离职 员工管理控制层: Controller RequestMapping("/employee") public class EmployeeController {Autowiredprivate IEmployeeService em…

转:async await 的前世今生 ; 异步 线程 多线程
写的非常好,改天搬过来
ubuntu14.04初体会
2014年4月17日ubuntu新的长期支持版14.04公布了,中国时间18日一早就能够下载到。18日晚。在我的X200上安装上了14.04,算是比較早一批体会到14.04正式版的人吧。对照12.04,14.04提升的执行速度非常明显,界面改善也是令人眼前一亮&a…

Linux 下获取本机所有网卡 以及 网卡对应ip 列表
简单record 一下 #include <arpa/inet.h> // struct sockaddr_in #include <errno.h> #include <net/if.h> // struct ifreq and struct if_nameindex #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/i…

Java项目:植物大战僵尸(java+swing)
源码获取:博客首页 "资源" 里下载! 功能简介: 植物大战僵尸、冒险模式、生存模式、解谜模式 小车服务类: public class CarThread extends Thread{private boolean flagtrue;private int x;private int y;private JL…

秋实大哥の恋爱物语
//裸kmp,劳资居然不会写!!!!!! 题意:中文题面自己看 解:差分裸kmp 因为可以上下移动,所以只要变化趋势相符就行,于是我们先做一个差分,…

《马哥出品高薪linux运维教程》wingkeung学习笔记-linux基础入门课程5
命令:内部命令:由shell程序自带的命令叫做内部命令;外部命令:在系统的某个路径下,有一个与命令同名的可执行程序叫做外部命令。查看内外部命令的命令:type 命令命令选项:用于调整命令执行行为的…

八、LaTex中的表格
转载于:https://www.cnblogs.com/invisible2/p/10813964.html

基于持久内存的 单机上亿(128B)QPS -- 持久化 k/v 存储引擎
文章目录性能数据设计背景设计架构Hash 索引结构 及 PMEM空间管理形态基本API 及 实现API初始化流程写流程读流程删除流程PMEM Allocator设计主要组件空间分配流程空间释放图数据库 on KVDK 性能性能数据 这个kv 存储引擎是持久化的存储引擎,存储介质是PMEM&#x…

SCALA当的trait
不是特别懂,但感觉和RUBY当中的MIX-IN功能有几分相似,这又扯到了多重继承及JAVA当中的接口虚拟类了。。 package com.hengheng.scalaclass UseTrait {} trait Logger {def log(msg : String) {println("log : " msg)} } trait ConsoleLogger …

Java项目:贪吃蛇游戏(java+swing)
源码获取:博客首页 "资源" 里下载! 功能简介: 贪吃蛇游戏 大嘴鱼洁面类。完成大嘴鱼的界面的绘制: /*** 大嘴鱼洁面类。完成大嘴鱼的界面的绘制。*/ public class BigMouthFishFrame extends JFrame{private FishPool pool null;…

使用Ext Form自动绑定Html中的Form元素
2019独角兽企业重金招聘Python工程师标准>>> Java代码 //把ext 对象绑定在Html Form元素时的ext属性中 Ext.override(Ext.Component, { initComponent :function(){ this.on(render, function(){ if(this.el) Ext.getDom(this.el).ext this; …

Directx11 教程(2) 基本的windows应用程序框架(2)
Directx11 教程(2) 基本的windows应用程序框架(2) 原文:Directx11 教程(2) 基本的windows应用程序框架(2)在本教程中,我们把前面一个教程的代码,进行封装。把初始化函数,Run函数,窗口回调函数,ShutdownWindows函数等封…

Rocksdb的事务(二):完整事务体系的 详细实现
文章目录1. 基本事务操作1.1 TransactionDB -- Pessimistic1.2 OptimisticTransactionDB1.3 Read Uncommitted1.4 SavePoint 回滚部分事务操作1.5 SetSnapshot1.6 GetForUpdate1.7 RepeatableRead2. 实现2.1 WBWI(write batch with index) & WB(write batch)2.2 Pessimisti…

关于学习编程的一些看法
1、看书,书上的代码一串一串的对吧?是不是很不好记?是不是觉得如果自己把这些代码都敲一遍很浪费时间?其实对于一些完全没有任何基础的人来说,全部敲一遍不失为一种简单的入门方法。对于有一点基础的人来说,…

Java项目:日历万年历(java+swing)
源码获取:博客首页 "资源" 里下载! 功能简介: 万年历 启动类: public class CalendarMainClass { public static void main(String args[]) { try { UIManager.setLookAndFeel("com.sun.java.swing.pl…

求大神给解释一下H3C ospf 双塔奇兵
转载于:https://blog.51cto.com/2807200/1364566

活着是为了什么?
活着是为了死亡,死亡才是完美,才是永恒。 死亡时将一无所有,所以活着不是为了能带走什么,而应该是能留下什么,这才是人活着的意义,多少人能想明白呢? 胡建龙转载于:https://www.cnblogs.com/hjl…

XFS 文件系统 (一) :设计概览
文章目录0 前言1 设计背景2. 需要解决的问题2.1 异常恢复太慢2.2 不支持大文件系统2.3 不支持大型稀疏文件2.4 不支持大型连续文件2.5 不支持大目录2.6 不支持过多文件个数3 XFS 架构4 痛点解决4.1 Allocation Groups4.2 Manging Free Space4.3 大文件的支持5 总结0 前言 虽然…

WebApi2官网学习记录---异常处理
HttpResponseException 当WebAPI的控制器抛出一个未捕获的异常时,默认情况下,大多数异常被转为status code为500的http response即服务端错误。 HttpResonseException是一个特别的情况,这个异常可以返回任意指定的http status code࿰…

Java项目:资源下载工具(java+swing)
源码获取:博客首页 "资源" 里下载! 功能简介: 下载地址、保存位置、下载设置、下载进度 文件仓库控制器: /*** ClassName: FileStoreController* Description: 文件仓库控制器**/ Controller public class FileStoreC…

江南Style之---西湖
西湖古称“钱塘湖”,又名“西子湖”,古代诗人苏轼就对它评价道:“欲把西湖比西子,淡妆浓抹总相宜。西湖,是一首诗,一幅天然图画,一个美丽动人的故事,不论是多年居住在这里的人还是匆…

mimikatz
下载后,在目标机直接运行 常用命令: 提升权限:privilege::debug 获取用户登录明文账号密码:sekurlsa::logonPasswords 获取用户密码hash值:lsadump::sam 转载于:https://www.cnblogs.com/xiaoqiyue/p/10824169.html

通过 RDTSC 指令从 CPU 寄存器中直接获取系统时钟
很多时候我们使用函数 gettimeofday 以及 clock_gettime 作为我们获取 wall lock的时钟函数。 因为这两种函数是 glibc 提供的用户封装,简单易用,而且能够精确到 ns,对于大多数的时钟需求场景都已经够用了。 但是如果 我们的应用 调用时钟频…