jemalloc 3.6.0源码详解—[1]Arena
来源:互联网 发布:2017中甲数据 编辑:程序博客网 时间:2024/05/29 11:46
转载自:vector03
2.2 Arena (arena_t)
如前所述, Arena是jemalloc中最大或者说最顶层的基础结构. 这个概念其实上是针对”对称多处理机(SMP)”产生的. 在SMP中, 导致性能劣化的一个重要原因在于”false sharing”导致cache-line失效.
为了解决cache-line共享问题, 同时保证更少的内部碎片(internal fragmentation), jemalloc使用了arena.
2.2.1 CPU Cache-Line
现代处理器为了解决内存总线吞吐量的瓶颈使用了内部cache技术. 尽管cache的工作机制很复杂, 且对外透明, 但在编程上, 还是有必要了解cache的基本性质.
Cache相当于嵌入到cpu内部的一组内存单元, 速度是主存的N倍, 但造价很高, 因此一般容量很小. 有些cpu设计了容量逐级逐渐增大的多级cache, 但速度逐级递减. 多级处理更复杂, 但原理类似, 为了简化, 仅讨论L1 data cache.
cache同主存进行数据交换有一个最小粒度, 称为cache-line, 通常这个值为64. 例如,在一个ILP32的机器上, 一次cache交换可以读写连续16个int型数据. 因此当访问数组#0元素时, 后面15个元素也被同时”免费”读到了cache中, 这对于数组的连续访问是非常有利的. 然而这种免费加载不总是会带来好处, 有时候甚至起到反效果, 所谓”false sharing”.
试想两个线程A和B分别执行在不同的cpu核心中,并分别操作各自上下文中的变量x和y.如果因为某种原因(比如x, y可能位于同一个class内部, 或者分别是数组中的两个相邻元素), 两者位于相同的cache-line中, 则在两个core的L1 cache里都存在x和y的副本. 倘若线程A修改了x的值, 就会导致在B中的x与A中看到的不一致. 尽管这个变量x对B可能毫无用处, 但cpu为了保证前后的正确和一致性, 只能判定core #1的cache失效. 因此core #0必须将cache-line回写到主存, 然后core #1再重新加载cache-line, 反之亦然. 如果恰好两个线程交替操作同一cache-line中的数据, 将对cache将造成极大的损害, 导致严重的性能退化.
说到底, 从程序的角度看, 变量是独立的地址单元, 但在CPU看来则是以cache-line为整体的单元. 单独的变量竞争可以在代码中增加同步来解决, 而cache-line的竞争是透明的, 不可控的, 只能被动由CPU仲裁. 这种观察角度和处理方式的区别, 正是false sharing的根源.
2.2.2 Arena原理
回到memory allocator的话题上. 对于一个多线程+多CPU核心的运行环境, 传统分配器中大量开销被浪费在lock contention和false sharing上, 随着线程数量和核心数量增多, 这种分配压力将越来越大.
针对多线程, 一种解决方法是将一把global lock分散成很多与线程相关的lock. 而针对多核心, 则要尽量把不同线程下分配的内存隔离开, 避免不同线程使用同一个cache-line的情况. 按照上面的思路, 一个较好的实现方式就是引入arena.
jemalloc将内存划分成若干数量的arenas, 线程最终会与某一个arena绑定. 比如上图中的threadA和B就分别绑定到arena #1和arena #3上. 由于两个arena在地址空间上几乎不存在任何联系, 就可以在无锁的状态下完成分配. 同样由于空间不连续, 落到同一个cache-line中的几率也很小, 保证了各自独立.
由于arena的数量有限, 因此不能保证所有线程都能独占arena, 比如, 图中threadA和C就都绑定到了arena1上. 分享同一个arena的所有线程, 由该arena内部的lock保持同步.
jemalloc将arena保存到一个数组中, 该数组全局记录了所有arenas,
arena_t **arenas;
事实上, 该数组是动态分配的, arenas仅仅是一个数组指针. 默认情况下arenas数组的长度与如下变量相关,
unsigned narenas_total;unsigned narenas_auto;size_t opt_narenas = 0;
而它们又与当前cpu核心数量相关. 核心数量记录在另外一个全局变量ncpus里,
unsigned ncpus;
如果ncpus等于1, 则有且仅有一个arena, 如果大于1, 则默认arenas的数量为ncpus的四倍. 即双核下8个arena, 四核下16个arena, 依此类推.
(gdb) p ncpus $20 = 4(gdb) p narenas_total$21 = 16
jemalloc变体很多, 不同变体对arenas的数量有所调整, 比如firefox中arena固定为1, 而android被限定为最大不超过2. 这个限制被写到android jemalloc的mk文件中.
2.2.3 choose_arena
最早引入arena并非由jemalloc首创, 但早期线程与arena绑定是通过hash线程id实现的, 相对来说随机性比较强. jemalloc改进了绑定的算法, 使之更加科学合理.
jemalloc中线程与arena绑定由函数choose_arena完成, 被绑定的arena记录在该线程的tls中,
JEMALLOC_INLINE arena_t *choose_arena(arena_t *arena){ ...... // xf: 通常情况下线程所绑定的arena记录在arenas_tls中 if ((ret = *arenas_tsd_get()) == NULL) { // xf: 如果当前thread未绑定arena, 则为其指定一个, 并保存到tls ret = choose_arena_hard(); } return (ret);}
初次搜索arenas_tsd_get可能找不到该函数在何处被定义. 实际上, jemalloc使用了一组宏, 来生成一个函数族, 以达到类似函数模板的目的. tsd相关的函数族被定义在tsd.h中.
- malloc_tsd_protos - 定义了函数声明, 包括初始化函数boot, get/set函数。
- malloc_tsd_externs - 定义变量声明, 包括tls, 初始化标志等等。
- malloc_tsd_data - tls变量定义。
- malloc_tsd_funcs - 定义了1中声明函数的实现。
与arena tsd相关的函数和变量声明如下,
malloc_tsd_protos(JEMALLOC_ATTR(unused), arenas, arena_t *)malloc_tsd_externs(arenas, arena_t *)malloc_tsd_data(, arenas, arena_t *, NULL)malloc_tsd_funcs(JEMALLOC_ALWAYS_INLINE, arenas, arena_t *, NULL, arenas_cleanup)
当线程还未与任何arena绑定时, 会进一步通过choose_arena_hard寻找一个合适的arena进行绑定. jemalloc会遍历arenas数组, 并按照优先级由高到低的顺序挑选,
- 如果找到当前线程绑定数为0的arena, 则优先使用它.
如果当前已初始化arena中没有线程绑定数为0的, 则优先使用剩余空的数组位置
构造一个新的arena. 需要说明的是, arenas数组遵循lazy create原则, 初始状态
整个数组只有0号元素是被初始化的, 其他的slot位置都是null指针. 因此随着新的
线程不断创造出来, arena数组也被逐渐填满.如果1,2两条都不满足, 则选择当前绑定数最小的, 且slot位置更靠前的一个arena.
arena_t * choose_arena_hard(void){ ...... if (narenas_auto > 1) { ...... first_null = narenas_auto; // xf: 循环遍历所有arenas, 找到绑定thread数量最小的arena, 并记录 // first_null索引值 for (i = 1; i < narenas_auto; i++) { if (arenas[i] != NULL) { if (arenas[i]->nthreads < arenas[choose]->nthreads) choose = i; } else if (first_null == narenas_auto) { first_null = i; } } // xf: 若选定的arena绑定thread为0, 或者当前arena数组中已满, 则返回 // 被选中的arena if (arenas[choose]->nthreads == 0 || first_null == narenas_auto) { ret = arenas[choose]; } else { // xf: 否则构造并初始化一个新的arena ret = arenas_extend(first_null); } ...... } else { // xf: 若不存在多于一个arena(单核cpu或人为强制设定), 则返回唯一的 // 0号arena ret = arenas[0]; ...... } // xf: 将已绑定的arena设置到tsd中 arenas_tsd_set(&ret); return (ret);}
对比早期的绑定方式, jemalloc的算法显然更加公平, 尽可能的让各个cpu核心平分当前线程,平衡负载.
2.2.4 Arena结构
struct arena_s { unsigned ind; unsigned nthreads; malloc_mutex_t lock; arena_stats_t stats; ql_head(tcache_t) tcache_ql; uint64_t prof_accumbytes; dss_prec_t dss_prec; arena_chunk_tree_t chunks_dirty; arena_chunk_t *spare; size_t nactive; size_t ndirty; size_t npurgatory; arena_avail_tree_t runs_avail; chunk_alloc_t *chunk_alloc; chunk_dalloc_t *chunk_dalloc; arena_bin_t bins[NBINS];};
ind: 在arenas数组中的索引值.
lock: 局部arena lock, 取代传统分配器的global lock. 一般地, 如下操作需要arena lock同步,
- 线程绑定, 需要修改nthreads.
- new chunk alloc.
- new run alloc.
stats: 全局统计, 需要打开统计功能.
tcache_ql: ring queue, 注册所有绑定线程的tcache, 作为统计用途, 需要打开统计功能.
dss_prec: 代表当前chunk alloc时对系统内存的使用策略, 分为几种情况,
typedef enum { dss_prec_disabled = 0, dss_prec_primary = 1, dss_prec_secondary = 2, dss_prec_limit = 3 } dss_prec_t; //第一个代表禁止使用系统DSS, 后两种代表是否优先选用DSS. 如果使用primary, // 则本着先dss->mmap的顺序, 否则按照先mmap->dss. 默认使用dss_prec_secondary.
chunks_dirty: rb tree(红黑树), 代表所有包含dirty page的chunk集合. 后面在chunk中会详细介绍.
spare: 是一个缓存变量, 记录最近一次被释放的chunk. 当arena收到一个新的chunk alloc请求时, 会优先从spare中开始查找, 由此提高频繁分配释放时, 可能导致内部chunk利用率下降的情况.
runs_avail: rb tree, 记录所有未被分配的runs, 用来在分配new run时寻找合适的available run. 一般作为alloc run时的仓库.
chunk_alloc/chunk_dalloc: 用户可定制的chunk分配/释放函数, jemalloc提供了默认的版本,
chunk_alloc_default/chunk_dalloc_default
- bins: bins数组, 记录不同class size可用free regions的分配信息, 后面会详细介绍.
- jemalloc 3.6.0源码详解—[1]Arena
- jemalloc 3.6.0源码详解—[0]基础知识
- jemalloc 3.6.0源码详解—[2]Chunk
- jemalloc 3.6.0源码详解—[3]Run and bins
- jemalloc 3.6.0源码详解—[4]Thread caches
- jemalloc 3.6.0源码详解—[5]分配及实现
- jemalloc 3.6.0源码详解—[6]释放及实现
- leveldb源码解析1——内存管理类Arena
- levelDB源码分析-Arena
- levelDB源码学习——Arena(简单内存池)
- jemalloc内存分配器详解
- arena
- jemalloc
- jemalloc
- jemalloc
- levelDB源码笔记(2)-Arena
- LevelDB源码剖析------------Arena内存管理
- LevelDB源码分析5-Arena.md
- Spring MVC RequestMapping
- 圆上的整数点
- 间谍网络--tarjan 解题报告
- Counting Cliques
- spring aop五种通知及通知中传递参数!
- jemalloc 3.6.0源码详解—[1]Arena
- LeetCode 题目总结/分类
- form表单数据交互(输出序列化表单值)
- 半导体器件 第五章PPT
- marathon-lb的高可用性
- PAT_乙级 1027 打印沙漏
- Codeforces 132E
- Win7旗舰版开机不需要输入密码登录
- LeetCode 12 Integer to Roman题解