Google在android5.0里用bionic中之默认分配器从Dl替换为JeGoogle在android5.0里将bionic中之默认分配器从Dl替换为Je

INSIDE OF
JEMALLOC
The Algorithm and Implementation of Jemalloc

INSIDE OF
JEMALLOC
The Algorithm and Implementation of Jemalloc

author: vector03
mail:   mmzsmm@163.com

author: vector03
mail:   mmzsmm@163.com

–[ Table of contents

–[ Table of contents

1 - 简介
 2 - Basic structures
   2.1 - Overview
   2.2 - Arena (arena_t)
     2.2.1 - CPU Cache-Line
     2.2.2 - Arena原理
     2.2.3 - choose_arena
     2.2.4 - Arena结构
   2.3 - Chunk (arena_chunk_t)
     2.3.1 - overview
     2.3.2 - chunk结构
     2.3.3 - chunk map (arena_chunk_map_t)
   2.4 - Run (arena_run_t)
     2.4.1 - run结构
     2.4.2 - size class
     2.4.3 - size2bin/bin2size
   2.5 - Bins (arena_bin_t)
   2.6 - Thread caches (tcache_t)
   2.7 - Extent Node (extent_node_t)
   2.8 - Base
 3 - Allocation
   3.1 - Overview
   3.2 - Initialize
   3.3 - small allocation (Arena)
     3.3.1 - arena_run_reg_alloc
     3.3.2 - arena_bin_malloc_hard
     3.3.3 - arena_bin_nonfull_run_get
     3.3.4 - small run alloc
     3.3.5 - chunk alloc
   3.4 - small allocation (tcache)
   3.5 - large allocation
   3.6 - huge allocation     
4 - Deallocation
   4.1 - Overview
   4.2 - arena_dalloc_bin
   4.3 - small run dalloc
   4.4 - arena purge    
   4.5 - arena chunk dalloc
   4.6 - large/huge dalloc
5 - 总结: 与Dl的对比
附: 快速调试Jemalloc
1 - 简介
 2 - Basic structures
   2.1 - Overview
   2.2 - Arena (arena_t)
     2.2.1 - CPU Cache-Line
     2.2.2 - Arena原理
     2.2.3 - choose_arena
     2.2.4 - Arena结构
   2.3 - Chunk (arena_chunk_t)
     2.3.1 - overview
     2.3.2 - chunk结构
     2.3.3 - chunk map (arena_chunk_map_t)
   2.4 - Run (arena_run_t)
     2.4.1 - run结构
     2.4.2 - size class
     2.4.3 - size2bin/bin2size
   2.5 - Bins (arena_bin_t)
   2.6 - Thread caches (tcache_t)
   2.7 - Extent Node (extent_node_t)
   2.8 - Base
 3 - Allocation
   3.1 - Overview
   3.2 - Initialize
   3.3 - small allocation (Arena)
     3.3.1 - arena_run_reg_alloc
     3.3.2 - arena_bin_malloc_hard
     3.3.3 - arena_bin_nonfull_run_get
     3.3.4 - small run alloc
     3.3.5 - chunk alloc
   3.4 - small allocation (tcache)
   3.5 - large allocation
   3.6 - huge allocation     
4 - Deallocation
   4.1 - Overview
   4.2 - arena_dalloc_bin
   4.3 - small run dalloc
   4.4 - arena purge    
   4.5 - arena chunk dalloc
   4.6 - large/huge dalloc
5 - 总结: 与Dl的对比
附: 快速调试Jemalloc

 

 

 

 

–[ 1 – 简介

–[ 1 – 简介

Jemalloc最初是Jason Evans为FreeBSD开发的新一代内存分配器,
用来代表原先的
phkmalloc, 最早投入使用是于2005年. 及目前为止, 除了原版Je,
还有为数不少变种
让用在各种类型里.
Google在android5.0里以bionic中之默认分配器从Dl替换为Je,
否是看中了其无坚不摧的多核多线程分配会力.

Jemalloc最初是Jason Evans为FreeBSD开发的新一代内存分配器,
用来代表原先的
phkmalloc, 最早投入使用是于2005年. 交目前为止, 除了原版Je,
还有很多变种
于用当各种档次里.
Google在android5.0里以bionic中之默认分配器从Dl替换为Je,
也是如意了那个强的多核多线程分配会力.

跟经典分配器, 如Dlmalloc相比, Je在基本思路和落实上存在明显的差别.
比如,
Dl在分配政策上支持于先dss后mmap的章程, 为的凡高效前进分配,
但Je则净相反.
而实现上呢放弃了经的boundary tag.
这些计划牺牲了一些分配速度与回收效率,
然而以又特别的空间和时间范围外也获得重新好的分红效果.

暨经典分配器, 如Dlmalloc相比, Je在基本思路和贯彻上有明显的差别.
比如,
Dl在分配政策及支持被先dss后mmap的章程, 为的是飞前进分配,
但Je则净相反.
假定落实达标为放弃了经的boundary tag.
这些规划牺牲了部分分配速度跟回收效率,
但以重复怪之空间和时间范围外也赢得更好之分配效果.

双重关键之是, 相对经典分配器,
Je最特别的优势还是那个精的多核/多线程分配会力.
为现代计算机硬件架构来说, 最老的瓶颈已经不复是内存容量或cpu速度, 而是
多核/多线程下的lock contention. 因为无核心数据如何多, 通常情况下内存
只生同份. 可以说, 如果内存足够充分, CPU的主干数据更为多, 程序线程数越多,
Je的分配速度更是快. 而这一点凡是经典分配器所无法上的.

重新着重的是, 相对经典分配器,
Je最充分的优势还是该强的多核/多线程分配会力.
因为现代电脑硬件架构来说, 最老的瓶颈已经不再是内存容量或cpu速度, 而是
多核/多线程下的lock contention. 因为不论核心数据如何多, 通常状态下内存
只是发同卖. 可以说, 如果内存足够好, CPU的中坚数据更为多, 程序线程数越多,
Je的分红速度更是快. 而立或多或少凡是经分配器所无法直达的.

当时首稿子基于android5.x中之Jemalloc3.6.0.
新颖的版本为4.x, 获取最新代码请到,

即首文章基于android5.x中之Jemalloc3.6.0.
风行的版也4.x, 获取最新代码请到,

https://github.com/jemalloc/jemalloc/releases

https://github.com/jemalloc/jemalloc/releases

 

 

–[ 2 –
Basic structures

–[ 2 –
Basic structures

对立于Dl, Je引入了重复多还复杂的分配结构, 如arena, chunk, bin, run,
region,
tcache等等. 其中多少接近Dl, 但更多的装有不同含义,
本节将本着它们做一一介绍.

对立于Dl, Je引入了还多更复杂的分红结构, 如arena, chunk, bin, run,
region,
tcache等等. 其中多少接近Dl, 但更多的富有不同含义,
本节将本着它们做一一介绍.

—-[ 2.1 – Overview

—-[ 2.1 – Overview

首先, 先给来一个整体的概念. Je对内存划分按照如下由大到低位之依次,

率先, 先给有一个完好无缺的概念. Je对内存划分按照如下由大交低位之相继,

  1. 内存是由自然数额之arenas进行管理.
  2. 一个arena被分成多chunks, 后者主要担负记录bookkeeping.
  3. chunk内部以富含着若干runs, 作为分配小片内存的核心单元.
  4. run由pages组成, 最终为分割成必然数量之regions,
  5. 于small size的分红要来说, 这些region就一定给user memory.
  1. 内存是由于必然数量的arenas进行管理.
  2. 一个arena被分割成几chunks, 后者主要担负记录bookkeeping.
  3. chunk内部又饱含在若干runs, 作为分配小片内存的骨干单元.
  4. run由pages组成, 最终让划分成自然数额的regions,
  5. 对small size的分配要来说, 这些region就相当给user memory.
    Arena #0
+----------------------------------------------------------------------------+
|                                                                            |
|    Chunk #0                             Chunk #1                           |
|  +---------------------------------+  +---------------------------------+  |
|  |                                 |  |                                 |  |
|  |   Run #0          Run #1        |  |   Run #0          Run #1        |  |
|  | +-------------+ +-------------+ |  | +-------------+ +-------------+ |  |
|  | |             | |             | |  | |             | |             | |  |
|  | |   Page      | |   Page      | |  | |   Page      | |   Page      | |  |
|  | | +---------+ | | +---------+ | |  | | +---------+ | | +---------+ | |  |
|  | | |         | | | |         | | |  | | |         | | | |         | | |  |
|  | | | Regions | | | | Regions | | |  | | | Regions | | | | Regions | | |  |
|  | | |[] [] [] | | | |[] [] [] | | |  | | |[] [] [] | | | |[] [] [] | | |  |
|  | | |         | | | |         | | |  | | |         | | | |         | | |  |
|  | | +---------+ | | +---------+ | |  | | +---------+ | | +---------+ | |  |  
|  | |             | |             | |  | |             | |             | |  |
|  | |   Page      | |   Page      | |  | |   Page      | |   Page      | |  |
|  | | +---------+ | | +---------+ | |  | | +---------+ | | +---------+ | |  |
|  | | |         | | | |         | | |  | | |         | | | |         | | |  |
|  | | | ...     | | | | ...     | | |  | | | ...     | | | | ...     | | |  |
|  | | +---------+ | | +---------+ | |  | | +---------+ | | +---------+ | |  |
|  | +-------------+ +-------------+ |  | +-------------+ +-------------+ |  |
|  +---------------------------------+  +---------------------------------+  |
+----------------------------------------------------------------------------+
    Arena #0
+----------------------------------------------------------------------------+
|                                                                            |
|    Chunk #0                             Chunk #1                           |
|  +---------------------------------+  +---------------------------------+  |
|  |                                 |  |                                 |  |
|  |   Run #0          Run #1        |  |   Run #0          Run #1        |  |
|  | +-------------+ +-------------+ |  | +-------------+ +-------------+ |  |
|  | |             | |             | |  | |             | |             | |  |
|  | |   Page      | |   Page      | |  | |   Page      | |   Page      | |  |
|  | | +---------+ | | +---------+ | |  | | +---------+ | | +---------+ | |  |
|  | | |         | | | |         | | |  | | |         | | | |         | | |  |
|  | | | Regions | | | | Regions | | |  | | | Regions | | | | Regions | | |  |
|  | | |[] [] [] | | | |[] [] [] | | |  | | |[] [] [] | | | |[] [] [] | | |  |
|  | | |         | | | |         | | |  | | |         | | | |         | | |  |
|  | | +---------+ | | +---------+ | |  | | +---------+ | | +---------+ | |  |  
|  | |             | |             | |  | |             | |             | |  |
|  | |   Page      | |   Page      | |  | |   Page      | |   Page      | |  |
|  | | +---------+ | | +---------+ | |  | | +---------+ | | +---------+ | |  |
|  | | |         | | | |         | | |  | | |         | | | |         | | |  |
|  | | | ...     | | | | ...     | | |  | | | ...     | | | | ...     | | |  |
|  | | +---------+ | | +---------+ | |  | | +---------+ | | +---------+ | |  |
|  | +-------------+ +-------------+ |  | +-------------+ +-------------+ |  |
|  +---------------------------------+  +---------------------------------+  |
+----------------------------------------------------------------------------+

 

 

—-[ 2.2 – Arena (arena_t)

—-[ 2.2 – Arena (arena_t)

如前所述, Arena是Je中极度深或说最顶层的底蕴结构.
这个概念其实齐是对”对如
多处理机(SMP)”产生的. 在SMP中, 导致性劣化的一个主要原由在于”false
sharing”
导致cache-line失效.

如前所述, Arena是Je中极度酷或说极端顶层的基础结构.
这个概念实际上齐是针对性”对如
多处理机(SMP)”产生的. 在SMP中, 导致性劣化的一个根本原由在”false
sharing”
导致cache-line失效.

为解决cache-line共享问题, 同时确保再也少的其中碎片(internal
fragmentation),
Je使用了arena.

以缓解cache-line共享问题, 同时保证还不见的里边碎片(internal
fragmentation),
Je使用了arena.

——[ 2.2.1 – CPU Cache-Line

——[ 2.2.1 – CPU Cache-Line

现代计算机为缓解内存总线吞吐量的瓶颈使用了里面cache技术. 尽管cache的
行事机制很复杂, 且对外透明, 但在编程上, 还是产生必要了解cache的基本性质.

当代电脑为化解内存总线吞吐量的瓶颈使用了中间cache技术. 尽管cache的
干活机制好复杂, 且对外透明, 但在编程上, 还是发必不可少了解cache的基本性质.

Cache是放到至cpu内部的一律组SRAM, 速度是主存的N倍, 但造价比高, 因此
一般容量非常小. 有些cpu设计了容量逐级逐渐增大的比比皆是cache,
但速度逐级递减.
星罗棋布处理又复杂, 但原理类似, 为了简化, 仅讨论L1 data cache.

Cache是坐至cpu内部的同组SRAM, 速度是主存的N倍, 但造价比高, 因此
相似容量非常小. 有些cpu设计了容量逐级逐渐增大的多元cache,
但速度逐级递减.
多元处理又复杂, 但原理类似, 为了简化, 仅讨论L1 data cache.

cache同主存进行数据交换有一个绝小粒度, 称为cache-line, 通常是价值也64.
诸如
当一个ILP32之机及, 一软cache交换可以读写连续16单int型数据.
因此当访问数组
#0元素时, 后面15独元素呢为以”免费”读到了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将促成巨大的损,
致使严重的特性退化.

但是这种免费加载不连续会带好处, 有时候甚至于及倒效果, 所谓”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将造成大的摧残,
招惨重的属性退化.

+-----------------------+        +-----------------------+
| core #0               |        | core #1               |
|                       |        |                       |
|  +----------+         |        |  +----------+         |
|  | ThreadA  |         |        |  | ThreadB  |         |
|  +----------+         |        |  +----------+         |
|        |              |        |        |              |
|    +---+              |        |        |              |
|    |                  |        |        |              |
|    v        D-cache   |        |        v     D-cache  |
|  +-----------------+  |        |  +-----------------+  |
|  | x'| y | ... ... | <---+  +---> | x | y'| ... ... |  |
|  |-----------------|  |  |  |  |  |-----------------|  |
|  |    ... ...      |  |  |  |  |  |    ... ...      |  |
|  |    ... ...      |  |  |  |  |  |    ... ...      |  |
|  |    ... ...      |  |  |  |  |  |    ... ...      |  |
|  +-----------------+  |  |  |  |  +-----------------+  |
+-----------------------+  |  |  +-----------------------+
                           |  |  
                    +------+  |
                    |         |
                    v         v
   memory   +-----------------------------
            | ... | x | y |     ... ...     
            +-----------------------------
+-----------------------+        +-----------------------+
| core #0               |        | core #1               |
|                       |        |                       |
|  +----------+         |        |  +----------+         |
|  | ThreadA  |         |        |  | ThreadB  |         |
|  +----------+         |        |  +----------+         |
|        |              |        |        |              |
|    +---+              |        |        |              |
|    |                  |        |        |              |
|    v        D-cache   |        |        v     D-cache  |
|  +-----------------+  |        |  +-----------------+  |
|  | x'| y | ... ... | <---+  +---> | x | y'| ... ... |  |
|  |-----------------|  |  |  |  |  |-----------------|  |
|  |    ... ...      |  |  |  |  |  |    ... ...      |  |
|  |    ... ...      |  |  |  |  |  |    ... ...      |  |
|  |    ... ...      |  |  |  |  |  |    ... ...      |  |
|  +-----------------+  |  |  |  |  +-----------------+  |
+-----------------------+  |  |  +-----------------------+
                           |  |  
                    +------+  |
                    |         |
                    v         v
   memory   +-----------------------------
            | ... | x | y |     ... ...     
            +-----------------------------

      
究竟, 从程序的角度看, 变量是独的地点单元,
但在CPU看来则是盖cache-line为
一体化的只是元. 单独的变量竞争可以在代码中增加并来缓解,
而cache-line的竞争是
晶莹剔透底, 不可控的, 只能被动由CPU仲裁. 这种观察角度和处理方式的分,
正是false
sharing的根源.

      
说到底, 从程序的角度看, 变量是独自的地址单元,
但在CPU看来则是以cache-line为
整的仅仅元. 单独的变量竞争可以于代码中追加一道来解决,
而cache-line的竞争是
透明的, 不可控的, 只能被动由CPU仲裁. 这种观察角度与处理方式的别,
正是false
sharing的根源.

——[ 2.2.2 – Arena原理

——[ 2.2.2 – Arena原理

回memory allocator的话题上. 对于一个多丝程+多CPU核心之运作环境, 传统
分配器中大量开发让浪费在lock contention和false sharing上, 随着线程数量
暨骨干数据加, 这种分配压力将越大.

回到memory allocator的话题上. 对于一个基本上线程+多CPU核心的运作环境, 传统
分配器中大量开发让浪费在lock contention和false sharing上, 随着线程数量
及基本数据多, 这种分配压力将尤其大.

本着多线程, 一栽缓解方式是拿同管global lock分散成多跟线程相关的lock.
如针对多为重, 则使尽可能把不同线程下分配的外存隔离开, 避免不同线程使用和
一个cache-line的情况. 按照地方的笔触, 一个较好的落实方式就是是引入arena.

对多线程, 一栽缓解办法是拿同管global lock分散成多同线程相关的lock.
如若针对多为重, 则要尽可能把不同线程下分配的外存隔离开, 避免不同线程使用和
一个cache-line的情况. 按照地方的笔触, 一个较好的贯彻方式就是是引入arena.

+---------+     +---------+     +---------+     +---------+     +---------+
| threadA |     | threadB |     | threadC |     | threadD |     | threadE |     
+---------+     +---------+     +---------+     +---------+     +---------+
     |               |               |               |               |
     |               +---------------|---------------|---------+     |
     +------------------+    +-------+        +------+         |     |
      +-----------------|----|----------------|----------------|-----+  
      |                 |    |                |                |
      v                 v    v                v                v
+----------+        +----------+        +----------+        +----------+     
|          |        |          |        |          |        |          |
| Arena #0 |        | Arena #1 |        | Arena #2 |        | Arena #3 |
|          |        |          |        |          |        |          |
+----------+        +----------+        +----------+        +----------+
+---------+     +---------+     +---------+     +---------+     +---------+
| threadA |     | threadB |     | threadC |     | threadD |     | threadE |     
+---------+     +---------+     +---------+     +---------+     +---------+
     |               |               |               |               |
     |               +---------------|---------------|---------+     |
     +------------------+    +-------+        +------+         |     |
      +-----------------|----|----------------|----------------|-----+  
      |                 |    |                |                |
      v                 v    v                v                v
+----------+        +----------+        +----------+        +----------+     
|          |        |          |        |          |        |          |
| Arena #0 |        | Arena #1 |        | Arena #2 |        | Arena #3 |
|          |        |          |        |          |        |          |
+----------+        +----------+        +----------+        +----------+

 

 

Je将内存划分成多数额之arenas, 线程最终见面和某个一个arena绑定.
比如达图中之
threadA和B就分别绑定到arena #1和#3达标.
由于个别独arena在地点空间上几未设有任何
联系, 就足以于无锁的状态下得分配. 同样出于空间不连续,
落到跟一个cache-line
受到的几乎率也颇粗, 保证了各自独立.

Je将内存划分成多多少的arenas, 线程最终见面暨某个一个arena绑定.
比如达图中的
threadA和B就各自绑定到arena #1和#3臻.
由于个别只arena在地方空间及几乎未设有其他
沟通, 就足以于无锁的状态下得分配. 同样出于空间不连续,
落到跟一个cache-line
屡遭的几乎带领为十分有点, 保证了个别独立.

出于arena的数码少于, 因此不能够保证有线程都能够独占arena, 比如,
图中threadA和C
就算还绑定到了arena1达标. 分享同一个arena的具有线程,
由该arena内部的lock保持同步.

由于arena的数目有限, 因此无克管所有线程都能够独占arena, 比如,
图中threadA和C
便都绑定到了arena1及. 分享同一个arena的兼具线程,
由该arena内部的lock保持同步.

Je将arena保存到一个数组中, 该数组全局记录了所有arenas,

Je将arena保存至一个数组中, 该数组全局记录了富有arenas,

arena_t            **arenas;

arena_t            **arenas;

实在, 该数组是动态分配的, arenas仅仅是一个数组指针.
默认情况下arenas数组的
长度和如下变量相关,

骨子里, 该数组是动态分配的, arenas仅仅是一个数组指针.
默认情况下arenas数组的
长度以及如下变量相关,

unsigned    narenas_total;
unsigned    narenas_auto;
size_t        opt_narenas = 0;
unsigned    narenas_total;
unsigned    narenas_auto;
size_t        opt_narenas = 0;

 

 

而它们又与目前cpu核心数据相关. 核心数据记录在另外一个全局变量ncpus里,

一旦其又与当下cpu核心数据相关. 核心数据记录在另外一个全局变量ncpus里,

unsigned    ncpus;

unsigned    ncpus;

如果ncpus等于1, 则闹还只来一个arena, 如果大于1,
则默认arenas的数额为ncpus的季倍.
不畏对审下8独arena, 四审下16个arena, 依此类推.

假设ncpus等于1, 则发且只发生一个arena, 如果大于1,
则默认arenas的数码也ncpus的季倍.
不怕双核查下8只arena, 四查核下16独arena, 依此类推.

(gdb) p ncpus
$20 = 4
(gdb) p narenas_total
$21 = 16
(gdb) p ncpus
$20 = 4
(gdb) p narenas_total
$21 = 16

 

 

 

 

jemalloc变体很多,
不同变体对arenas的数量有调整, 比如firefox中arena固定为1,
要android被拘为极端酷未跳2. 这范围受勾勒到android
jemalloc的mk文件中.

jemalloc变体很多,
不同变体对arenas的多寡有调整, 比如firefox中arena固定为1,
一旦android被界定为最深未超越2. 夫范围于描绘到android
jemalloc的mk文件中.

——[ 2.2.3 – choose_arena

——[ 2.2.3 – choose_arena

尽早引入arena并非由于Je首创, 但早期线程与arena绑定是由此hash线程id实现之,
相对
来说随机性比较强. Je改进了绑定的算法, 使之更加科学合理.

最好早引入arena并非出于Je首创, 但早期线程与arena绑定是通过hash线程id实现之,
相对
来说随机性比较强. Je改进了绑定的算法, 使之更科学合理.

Je中线程与arena绑定由函数choose_arena完成,
被绑定的arena记录在拖欠线程的tls中,

Je中线程与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);
}
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可能寻不顶该函数以何方被定义.
实际上, Je使用了相同组宏,
来生成一个函数族, 以达近似函数模板的目的.
tsd相关的函数族被定义在tsd.h中.

首位搜索arenas_tsd_get可能找不交该函数以哪儿被定义.
实际上, Je使用了同等组宏,
来生成一个函数族, 以达到近似函数模板的目的.
tsd相关的函数族被定义在tsd.h中.

  1. malloc_tsd_protos – 定义了函数声明, 包括初始化函数boot,
    get/set函数
  2. malloc_tsd_externs – 定义变量声明, 包括tls, 初始化标志等等
  3. malloc_tsd_data – tls变量定义
  4. malloc_tsd_funcs – 定义了1遭遇声明函数的实现.
  1. malloc_tsd_protos – 定义了函数声明, 包括初始化函数boot,
    get/set函数
  2. malloc_tsd_externs – 定义变量声明, 包括tls, 初始化标志等等
  3. malloc_tsd_data – tls变量定义
  4. malloc_tsd_funcs – 定义了1中声称函数的贯彻.

与arena tsd相关的函数和变量声明如下,

暨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)
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
展开绑定. Je会遍历arenas数组, 并按照优先级由大到没有的依次挑选,

当线程还非跟任何arena绑定时,
会进一步通过choose_arena_hard寻找一个老少咸宜的arena
拓展绑定. Je会遍历arenas数组, 并按照先级由高至小的各个挑选,

  1. 假定找到时线程绑定数为0之arena, 则优先利用它.
  2. 要是手上一度初始化arena中没线程绑定数为0的,
    则优先利用剩余空的数组位置
       构造一个新的arena. 需要说明的是, arenas数组遵循lazy create原则,
    初始状态
       整个数组只有0如泣如诉元素是给初始化的, 其他的slot位置都是null指针.
    因此就初的
       线程不断创出来, arena数组也被渐渐填充满.
  3. 使1,2个别漫漫都无满足, 则选择时绑定数最好小之,
    且slot位置还因前的一个arena.
  1. 只要找到时线程绑定数为0的arena, 则优先利用它.
  2. 如若手上既初始化arena中尚无线程绑定数为0底,
    则优先使用剩余空的数组位置
       构造一个初的arena. 需要证明的凡, arenas数组遵循lazy create原则,
    初始状态
       整个数组只有0哀号元素是于初始化的, 其他的slot位置还是null指针.
    因此趁初的
       线程不断创出, arena数组也深受日益填充满.
  3. 比方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);
}
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);
}

 

 

 

 

比早期的绑定方式, Je的算法显然更公平,
尽可能的吃各个cpu核心平分当前线程,
抵负载.

对照早期的绑定方式, Je的算法显然更公正,
尽可能的被各个cpu核心平分当前线程,
抵负载.

——[ 2.2.4 – Arena结构

——[ 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];
};
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数组中的目录值.

ind:
在arenas数组中之目值.

nthreads: 当前绑定的线程数.

nthreads: 当前绑定的线程数.

lock: 局部arena lock, 取代传统分配器的global lock.
      一般地, 如下操作需要arena lock同步,
      1. 线程绑定, 需要改nthreads
      2. new chunk alloc
      3. new run alloc
      
stats: 全局统计, 需要开拓统计功能.

lock: 局部arena lock, 取代传统分配器的global lock.
      一般地, 如下操作需要arena lock同步,
      1. 线程绑定, 需要改nthreads
      2. new chunk alloc
      3. new run alloc
      
stats: 全局统计, 需要开辟统计功能.

tcache_ql: ring queue, 注册所有绑定线程的tcache, 作为统计用途,
需要开辟统计功能.

tcache_ql: ring queue, 注册所有绑定线程的tcache, 作为统计用途,
需要开辟统计功能.

dss_prec: 代表时chunk alloc时对网内存的运政策,
分为几种植状态,     

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;
          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.

         
第一只代表禁止用系统DSS, 后片种表示是否先选用DSS. 如果用
          primary, 则本着先dss->mmap的次第, 否则仍先mmap->dss.
默认使用
          dss_prec_secondary.

chunks_dirty: rb tree, 代表有包含dirty page的chunk集合.
后面在chunk中会
              详细介绍.

chunks_dirty: rb tree, 代表有包含dirty page的chunk集合.
后面在chunk中会
              详细介绍.

spare: 是一个缓存变量, 记录以来平次于受放走的chunk.
当arena收到一个初的chunk
       alloc请求时, 会优先由spare中开始查找, 由此增强往往分配释放时,
可能
       导致内chunk利用率下降的情况.

spare: 是一个缓存变量, 记录以来同等潮让放飞的chunk.
当arena收到一个新的chunk
       alloc请求时, 会优先从spare中开始查找, 由此增强往往分配释放时,
可能
       导致其中chunk利用率下降之情况.

runs_avail: rb tree, 记录有不吃分配的runs, 用来在分配new
run时追寻适合的
            available run. 一般作为alloc run时的仓库.

runs_avail: rb tree, 记录有未为分配的runs, 用来在分配new
run时寻找合适的
            available run. 一般作为alloc run时之仓库.

chunk_alloc/chunk_dalloc: 用户可定制的chunk分配/释放函数,
Je提供了默认的本,
                          chunk_alloc_default/chunk_dalloc_default

chunk_alloc/chunk_dalloc: 用户可定制的chunk分配/释放函数,
Je提供了默认的本子,
                          chunk_alloc_default/chunk_dalloc_default

bins: bins数组, 记录不同class size可用free regions的分配信息,
后面会详细介绍.

bins: bins数组, 记录不同class size可用free regions的分红信息,
后面会详细介绍.

—-[ 2.3 – Chunk (arena_chunk_t)

—-[ 2.3 – Chunk (arena_chunk_t)

chunk是仅次于arena的次级内存结构. 如果发生打探过Dlmalloc,
就会见分晓当Dl中同样
概念了名为也’chunk’的底子结构. 但这个定义在片只分配器中含义完全两样,
Dl中的
chunk指代最低级分配单元, 而Je中虽然是一个比充分的内存区域.

chunk是仅次于arena的次级内存结构. 如果生了解过Dlmalloc,
就会分晓当Dl中一律
概念了号称也’chunk’的基础结构. 但此定义在少数个分配器中含义完全不同,
Dl中的
chunk指代最低级分配单元, 而Je中则是一个较生之内存区域.

——[ 2.3.1 – overview

——[ 2.3.1 – overview

由眼前arena的数据结构可以窥见, 它是一个颇抽象的定义,
其尺寸为未意味着实际的
内存分配量. 原始之内存数据既是非挂载在arena外部,
也并无经内指针引用,
而是记录在chunk中. 按照一般的笔触, chunk包含原始内存数据,
又从属于arena,
为此后者应该会有一个数组之类的构造以记录有chunk信息.
但实在同样找不顶
这样的记录. 那Je又怎么取得chunk指针也?

由眼前arena的数据结构可以发现, 它是一个十分抽象的定义,
其尺寸为不意味实际的
内存分配量. 原始的内存数据既是未挂载在arena外部,
也并没经中间指针引用,
而是记录在chunk中. 按照一般的思绪, chunk包含原始内存数据,
又于属于arena,
故此后者应该会发出一个数组之类的组织为记录有chunk信息.
但实际同样招来不顶
然的记录. 那Je又哪收获chunk指针也?

所谓的chunk结构, 只是一体chunk的一个header, bookkeeping以及user
memory都挂于
header外面. 另外Je对chunk又做了确定, 默认每个chunk大小为4MB,
同时还须对旅
至4MB底边际上.

所谓的chunk结构, 只是浑chunk的一个header, bookkeeping以及user
memory都挂于
header外面. 另外Je对chunk又做了确定, 默认每个chunk大小为4MB,
同时还必须对同
暨4MB之边际上.

#define    LG_CHUNK_DEFAULT    22
#define    LG_CHUNK_DEFAULT    22

 

 

以此宏定义了chunk的大小. 注意到面前缀’LG_’, 代表log即指数部分.
Je中享有拖欠前缀的代码都是其一含义, 便于通过bit操作进行高效的运算.

夫宏定义了chunk的大小. 注意到面前缀’LG_’, 代表log即指数部分.
Je中装有拖欠前缀的代码都是者义, 便于通过bit操作进行高效的运算.

发出矣上述规定, 获得chunk就更换得几乎没有代价.
因为归给user程序的内存地址肯定
属有chunk, 而拖欠chunk header对旅到4M疆上, 且不容许超越4M轻重缓急,
所以只待
针对该地点做一个下蛋本着一起就取chunk指针, 如下,

生矣上述规定, 获得chunk就易得几乎从未代价.
因为归给user程序的内存地址肯定
属于有chunk, 而拖欠chunk header对旅到4M疆上, 且不容许过4M尺寸,
所以只待
对该地址做一个生本着一头就收获chunk指针, 如下,

#define    CHUNK_ADDR2BASE(a)                        \
    ((void *)((uintptr_t)(a) & ~chunksize_mask))
#define    CHUNK_ADDR2BASE(a)                        \
    ((void *)((uintptr_t)(a) & ~chunksize_mask))

 
计算相对于chunk header的偏移量,

 
计量相对于chunk header的偏移量,

#define    CHUNK_ADDR2OFFSET(a)                        \
    ((size_t)((uintptr_t)(a) & chunksize_mask))
#define    CHUNK_ADDR2OFFSET(a)                        \
    ((size_t)((uintptr_t)(a) & chunksize_mask))

 
以及达对同到chunk边界的盘算,

 
同达针对同步到chunk边界的计量,

#define    CHUNK_CEILING(s)                        \
    (((s) + chunksize_mask) & ~chunksize_mask)
#define    CHUNK_CEILING(s)                        \
    (((s) + chunksize_mask) & ~chunksize_mask)

 

 

之所以图来代表如下,   

为此图来表示如下,   

   chunk_ptr(4M aligned)                memory for user
    |                                   |
    v                                   v
    +--------------+--------------------------------------------
    | chunk header |        ... ...     | region |   ... ...     
    +--------------+--------------------------------------------
    |<------------- offset ------------>|
   chunk_ptr(4M aligned)                memory for user
    |                                   |
    v                                   v
    +--------------+--------------------------------------------
    | chunk header |        ... ...     | region |   ... ...     
    +--------------+--------------------------------------------
    |<------------- offset ------------>|

 

 

——[ 2.3.2 – Chunk结构

——[ 2.3.2 – Chunk结构

struct arena_chunk_s {
    arena_t            *arena;
    rb_node(arena_chunk_t)    dirty_link;
    size_t            ndirty;
    size_t            nruns_avail;
    size_t            nruns_adjac;
    arena_chunk_map_t    map[1];
}
struct arena_chunk_s {
    arena_t            *arena;
    rb_node(arena_chunk_t)    dirty_link;
    size_t            ndirty;
    size_t            nruns_avail;
    size_t            nruns_adjac;
    arena_chunk_map_t    map[1];
}

 

 

arena:
chunk属于哪个arena

arena:
chunk属于哪个arena

dirty_link: 用于rb tree的链接节点. 如果有chunk内部含有其他dirty
page,
            就会见为挂载到arena中的chunks_dirty tree上.

dirty_link: 用于rb tree的链接节点. 如果某chunk内部含有其他dirty
page,
            就会受挂载到arena中的chunks_dirty tree上.

ndirty: 内部dirty page数量.

ndirty: 内部dirty page数量.

nruns_avail: 内部available runs数量.

nruns_avail: 内部available runs数量.

nruns_adjac: available runs又分为dirty和clean两栽,
相邻之一定量种植run是无力回天统一之,
             除非中的dirty runs通过purge才可以.
该数值记录的就是是好透过
             purge合并的run数量.

nruns_adjac: available runs又分为dirty和clean两种植,
相邻之有数种run是力不从心统一的,
             除非内的dirty runs通过purge才可以.
该数值记录之虽是可以通过
             purge合并的run数量.

map: 动态数组,
每一样桩针对应chunk中之一个page状态(不含有header即map本身的占用).
     chunk(包括中的run)都是出于page组成的. page又分为unallocated,
small,
     large三种.
     unallocated指的那些还免建run的page.
     small/large分别代表该page所属run的门类是small/large run.
     这些page的分红状态, 属性, 偏移量, 及其他的标志信息等等, 都记录在
     arena_chunk_map_t中.

map: 动态数组,
每一样码对应chunk中之一个page状态(不含有header即map本身的挤占).
     chunk(包括内部的run)都是出于page组成的. page又分为unallocated,
small,
     large三种.
     unallocated指的那些还未成立run的page.
     small/large分别代表该page所属run的项目是small/large run.
     这些page的分配状态, 属性, 偏移量, 及其它的标记信息等等, 都记录在
     arena_chunk_map_t中.

|<--------- map_bias ----------->|
| page | page |  ... ...  | page |
+-----------------------------------------------------------------------+
| chunk_header |    chunk map    | page #0  | page #1  | ... | page #n  |
|    ... ...   | [0] [1] ... [n] |          |          |     |          |
+-----------------|---|-------|-----------------------------------------+
                  |   |       |       ^          ^                 ^
                  +---|-------|-------+          |                 |
                      +-------|------------------+                 |
                              + -----------------------------------+
|<--------- map_bias ----------->|
| page | page |  ... ...  | page |
+-----------------------------------------------------------------------+
| chunk_header |    chunk map    | page #0  | page #1  | ... | page #n  |
|    ... ...   | [0] [1] ... [n] |          |          |     |          |
+-----------------|---|-------|-----------------------------------------+
                  |   |       |       ^          ^                 ^
                  +---|-------|-------+          |                 |
                      +-------|------------------+                 |
                              + -----------------------------------+

 
至于由chunk header和chunk map占用的page数量, 保存在map_bias变量中.
欠变量是Je在arena boot时通过迭代算法预先计算好的, 所有chunk都是均等之.
迭代方如下,

 
至于由chunk header和chunk map占用的page数量, 保存在map_bias变量中.
拖欠变量是Je在arena boot时通过迭代算法预先计算好的, 所有chunk都是一样之.
迭代法如下,

  1. 第一次迭代初始map_bias等于0, 计算最充分或大小, 即
       header_size + chunk_npages * map_size
       获得header+map需要的page数量, 结果一定高于最终之值.
  2. 第二潮以之前算的map_bias迭代回来, 将最为充分page数减去map_bias数,
    重新计算
       header+map大小, 由于第一蹩脚迭代map_bias过那个,
    第二涂鸦迭代必定小于最终结果.
  3. 老三次等再以map_bias迭代归来,
    得到最终压倒第二坏都小于第一坏的计算结果.
  1. 首先差迭代初始map_bias等于0, 计算最深或大小, 即
       header_size + chunk_npages * map_size
       获得header+map需要的page数量, 结果必然过最终之值.
  2. 老二赖以前算的map_bias迭代归来, 将最老page数减去map_bias数,
    重新计算
       header+map大小, 由于第一不行迭代map_bias过死,
    第二糟糕迭代必定小于最终结果.
  3. 其三蹩脚又用map_bias迭代回到,
    得到终极胜出第二次等还低于第一不成的计算结果.

相关代码如下,

有关代码如下,

void
arena_boot(void)
{
    ......
    map_bias = 0;
    for (i = 0; i < 3; i++) {
        header_size = offsetof(arena_chunk_t, map) +
            (sizeof(arena_chunk_map_t) * (chunk_npages-map_bias));
        map_bias = (header_size >> LG_PAGE) + ((header_size & PAGE_MASK)
            != 0);
    }
    ......
}
void
arena_boot(void)
{
    ......
    map_bias = 0;
    for (i = 0; i < 3; i++) {
        header_size = offsetof(arena_chunk_t, map) +
            (sizeof(arena_chunk_map_t) * (chunk_npages-map_bias));
        map_bias = (header_size >> LG_PAGE) + ((header_size & PAGE_MASK)
            != 0);
    }
    ......
}

 

 

——[ 2.3.3 – chunk map (arena_chunk_map_t)

——[ 2.3.3 – chunk map (arena_chunk_map_t)

chunk记录page状态的布局为arena_chunk_map_t, 为了节约空间,
使用了bit压缩存储信息.

chunk记录page状态的布局也arena_chunk_map_t, 为了节省空间,
使用了bit压缩存储信息.

struct arena_chunk_map_s {
#ifndef JEMALLOC_PROF
    union {
#endif
    union {
        rb_node(arena_chunk_map_t)    rb_link;
        ql_elm(arena_chunk_map_t)    ql_link;
    }                u;
    prof_ctx_t            *prof_ctx;
#ifndef JEMALLOC_PROF
    };
#endif
    size_t                bits;
}
struct arena_chunk_map_s {
#ifndef JEMALLOC_PROF
    union {
#endif
    union {
        rb_node(arena_chunk_map_t)    rb_link;
        ql_elm(arena_chunk_map_t)    ql_link;
    }                u;
    prof_ctx_t            *prof_ctx;
#ifndef JEMALLOC_PROF
    };
#endif
    size_t                bits;
}

 
chunk map内部包含两独link node, 分别可挂载到rb tree或环形队列上,
同时
以节约空间又下了union. 由于run本身为是由连续page组成的, 因此chunk
map
除去记录page状态之外, 还担当run的基址检索.

 
chunk map内部包含两个link node, 分别可以挂载到rb tree或环形队列上,
同时
为省空间又采取了union. 由于run本身为是由连接page组成的, 因此chunk
map
除开记录page状态之外, 还负责run的基址检索.

比方来说, Je会把具备曾经分配run记录在里头rb tree上坐很快搜索,
实际地操作是
将欠run中首先单page对应的chunk_map作为rb node挂载到tree上.
检索时为是先行
摸有用相应的chunk map, 再开展地址转换得到run的基址.

举例来说来说, Je会把有都分配run记录在内部rb tree上以快速搜索,
实际地操作是
将拖欠run中第一单page对应的chunk_map作为rb node挂载到tree上.
检索时为是先期
寻来以相应的chunk map, 再进行地址转换得到run的基址.

随通常的计划性思路, 我们或许会见拿run指针作为节点直接保存及rb tree中.
但Je中
的统筹引人注目要又复杂. 究其原因, 如果把link node放到run中,
后果是bookkeeping和
user memory将混淆在协同, 这对分配器的安全性是颇不利的.
包括Dl在内的风土民情
分配器都持有如此的缺陷. 而只要单独用link node记录run,
又见面招致空间浪费.
凑巧缘Je中不管chunk还是run都是连续page组成, 所以用首独page对应的chunk
map
即可知以意味着该run的基址.

依照一般的宏图思路, 我们或会见拿run指针作为节点直接保存到rb tree中.
但Je中
的筹划引人注目使更复杂. 究其原因, 如果把link node放到run中,
后果是bookkeeping和
user memory将混淆在一块儿, 这对分配器的安全性是充分不利的.
包括Dl在内的风俗习惯
分配器都兼备这样的缺陷. 而若单独用link node记录run,
又见面招致空间浪费.
碰巧为Je中管chunk还是run都是连page组成, 所以用首只page对应之chunk
map
即使会而表示该run的基址.

Je中日常用mapelm换算出pageind, 再以pageind << LG_PAGE +
chunk_base, 就会得
run指针, 代码如下,

Je中一般用mapelm换算出pageind, 再以pageind << LG_PAGE +
chunk_base, 就会得到
run指针, 代码如下,

arena_chunk_t *run_chunk = CHUNK_ADDR2BASE(mapelm);
size_t pageind = arena_mapelm_to_pageind(mapelm);
run = (arena_run_t *)((uintptr_t)run_chunk + (pageind <<
    LG_PAGE));

JEMALLOC_INLINE_C size_t
arena_mapelm_to_pageind(arena_chunk_map_t *mapelm)
{
    uintptr_t map_offset =
        CHUNK_ADDR2OFFSET(mapelm) - offsetof(arena_chunk_t, map);

    return ((map_offset / sizeof(arena_chunk_map_t)) + map_bias);
}
arena_chunk_t *run_chunk = CHUNK_ADDR2BASE(mapelm);
size_t pageind = arena_mapelm_to_pageind(mapelm);
run = (arena_run_t *)((uintptr_t)run_chunk + (pageind <<
    LG_PAGE));

JEMALLOC_INLINE_C size_t
arena_mapelm_to_pageind(arena_chunk_map_t *mapelm)
{
    uintptr_t map_offset =
        CHUNK_ADDR2OFFSET(mapelm) - offsetof(arena_chunk_t, map);

    return ((map_offset / sizeof(arena_chunk_map_t)) + map_bias);
}

 
chunk map对page状态描述都减少记录到bits中, 由于内容比较多,
直接引用Je代码
丁的笺注,

 
chunk map对page状态描述都减掉记录及bits中, 由于内容比较多,
直接引用Je代码
惨遭之笺注,

  下面是一个假想的ILP32系下之bits layout,

  下面是一个设的ILP32网下之bits layout,

  ???????? ???????? ????nnnn nnnndula

  ???????? ???????? ????nnnn nnnndula

  “?”的片区划三栽状态, 分别针对许unallocated, small和large.
  ? : Unallocated: 首尾page写副该run的地方, 而内部page则无举行要求.
      Small: 全部凡page的偏移量.
      Large: 首page是run size, 后上的page不做要求.
  n : 对于small run指其所在bin的index, 对large run写入BININD_INVALID.
  d : dirty?
  u : unzeroed?
  l : large?
  a : allocated?

  “?”的片分三种情景, 分别指向承诺unallocated, small和large.
  ? : Unallocated: 首尾page写副该run的地方, 而内部page则未做要求.
      Small: 全部凡page的偏移量.
      Large: 首page是run size, 后加的page不做要求.
  n : 对于small run指其所在bin的index, 对large run写入BININD_INVALID.
  d : dirty?
  u : unzeroed?
  l : large?
  a : allocated?

  下面是本着三种植档次的run page做的比方,

  下面是指向三栽档次的run page做的比方,

  p : run page offset
  s : run size
  n : binind for size class; large objects set these to
BININD_INVALID
  x : don’t care
  – : 0
  + : 1
  [DULA] : bit set
  [dula] : bit unset

  p : run page offset
  s : run size
  n : binind for size class; large objects set these to
BININD_INVALID
  x : don’t care
  – : 0
  + : 1
  [DULA] : bit set
  [dula] : bit unset

  Unallocated (clean):
    ssssssss ssssssss ssss++++ ++++du-a
    xxxxxxxx xxxxxxxx xxxxxxxx xxxx-Uxx
    ssssssss ssssssss ssss++++ ++++dU-a

  Unallocated (clean):
    ssssssss ssssssss ssss++++ ++++du-a
    xxxxxxxx xxxxxxxx xxxxxxxx xxxx-Uxx
    ssssssss ssssssss ssss++++ ++++dU-a

  Unallocated (dirty):
    ssssssss ssssssss ssss++++ ++++D–a
    xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
    ssssssss ssssssss ssss++++ ++++D–a

  Unallocated (dirty):
    ssssssss ssssssss ssss++++ ++++D–a
    xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
    ssssssss ssssssss ssss++++ ++++D–a

  Small:      
    pppppppp pppppppp ppppnnnn nnnnd–A
    pppppppp pppppppp ppppnnnn nnnn—A
    pppppppp pppppppp ppppnnnn nnnnd–A
    
    Small page需要专注的凡, 这里表示的p并非是一个固定值,
而是该page相对于
    其所在run的率先独page的偏移量, 比如可能是这般,
    00000000 00000000 0000nnnn nnnnd–A
    00000000 00000000 0001nnnn nnnn—A
    00000000 00000000 0010nnnn nnnn—A
    00000000 00000000 0011nnnn nnnn—A
    …
    00000000 00000001 1010nnnn nnnnd–A

  Small:      
    pppppppp pppppppp ppppnnnn nnnnd–A
    pppppppp pppppppp ppppnnnn nnnn—A
    pppppppp pppppppp ppppnnnn nnnnd–A
    
    Small page需要留意的是, 这里代表的p并非是一个固定值,
而是该page相对于
    其所在run的率先单page的偏移量, 比如可能是这么,
    00000000 00000000 0000nnnn nnnnd–A
    00000000 00000000 0001nnnn nnnn—A
    00000000 00000000 0010nnnn nnnn—A
    00000000 00000000 0011nnnn nnnn—A
    …
    00000000 00000001 1010nnnn nnnnd–A

  Large:
    ssssssss ssssssss ssss++++ ++++D-LA
    xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
    ——– ——– —-++++ ++++D-LA

  Large:
    ssssssss ssssssss ssss++++ ++++D-LA
    xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
    ——– ——– —-++++ ++++D-LA

  Large (sampled, size <= PAGE):
    ssssssss ssssssss ssssnnnn nnnnD-LA

  Large (sampled, size <= PAGE):
    ssssssss ssssssss ssssnnnn nnnnD-LA

  Large (not sampled, size == PAGE):
    ssssssss ssssssss ssss++++ ++++D-LA

  Large (not sampled, size == PAGE):
    ssssssss ssssssss ssss++++ ++++D-LA

为取/设置map bits内部的音讯, Je提供了平等组函数,
这里列举两单极中心的,
结余的都是读取mapbits后做一些员运算而已,

以取/设置map bits内部的消息, Je提供了同样组函数,
这里列举两个极端中心的,
剩余的且是读取mapbits后召开有员运算而已,

读取mapbits,

读取mapbits,

JEMALLOC_ALWAYS_INLINE size_t
arena_mapbits_get(arena_chunk_t *chunk, size_t pageind)
{
    return (arena_mapbitsp_read(arena_mapbitsp_get(chunk, pageind)));
}
JEMALLOC_ALWAYS_INLINE size_t
arena_mapbits_get(arena_chunk_t *chunk, size_t pageind)
{
    return (arena_mapbitsp_read(arena_mapbitsp_get(chunk, pageind)));
}

 

 

依据pageind获取相应之chunk map,

基于pageind获取相应的chunk map,

JEMALLOC_ALWAYS_INLINE arena_chunk_map_t *
arena_mapp_get(arena_chunk_t *chunk, size_t pageind)
{
    ......
    return (&chunk->map[pageind-map_bias]);
}
JEMALLOC_ALWAYS_INLINE arena_chunk_map_t *
arena_mapp_get(arena_chunk_t *chunk, size_t pageind)
{
    ......
    return (&chunk->map[pageind-map_bias]);
}

 

 

 

 

—-[ 2.4 – Run (arena_run_t)

—-[ 2.4 – Run (arena_run_t)

似在2.1节所述, 在Je中run才是实在担负分配的重心(前提是针对small
region来说).
run的轻重缓急对合到page size上, 并且在内部划分成大大小小相同之region.
当起表面分配
伸手时, run就见面从里头甄选一个free region返回. 可以看run就是small
region仓库.

似在2.1节所述, 在Je中run才是当真负责分配的侧重点(前提是指向small
region来说).
run的分寸对旅到page size上, 并且在里划分成大大小小同等的region.
当起表面分配
求时, run就会见起里面甄选一个free region返回. 可以当run就是small
region仓库.

——[ 2.4.1 – Run结构

——[ 2.4.1 – Run结构

struct arena_run_s {
    arena_bin_t    *bin;
    uint32_t    nextind;
    unsigned    nfree;
};
struct arena_run_s {
    arena_bin_t    *bin;
    uint32_t    nextind;
    unsigned    nfree;
};

 

 

run的组织非常简单, 但同chunk类似,
所谓的arena_run_t不过是一体run的header部分.

run的构造非常简单, 但同chunk类似,
所谓的arena_run_t不过是合run的header部分.

bin:     与该run相关联的bin. 每个run都发出夫所属的bin,
详细内容以随后介绍.
nextind: 记录下一个clean region的索引.
nfree:   记录时空闲region数量.

bin:     与该run相关联的bin. 每个run都发那个所属的bin,
详细内容以之后介绍.
nextind: 记录下一个clean region的索引.
nfree:   记录时空闲region数量.

除了header部分外, run的真实layout如下,

除去header部分以外, run的真实layout如下,

               /--------------------\
               | arena_run_t header |
               | ...                |
 bitmap_offset | bitmap             |
               | ...                |
               |--------------------|
               | redzone            |
   reg0_offset | region 0           |
               | redzone            |
               |--------------------| \
               | redzone            | |
               | region 1           |  > reg_interval
               | redzone            | /
               |--------------------|
               | ...                |
               | ...                |
               | ...                |
               |--------------------|
               | redzone            |
               | region nregs-1     |
               | redzone            |
               |--------------------|
               | alignment pad?     |
               \--------------------/
               /--------------------\
               | arena_run_t header |
               | ...                |
 bitmap_offset | bitmap             |
               | ...                |
               |--------------------|
               | redzone            |
   reg0_offset | region 0           |
               | redzone            |
               |--------------------| \
               | redzone            | |
               | region 1           |  > reg_interval
               | redzone            | /
               |--------------------|
               | ...                |
               | ...                |
               | ...                |
               |--------------------|
               | redzone            |
               | region nregs-1     |
               | redzone            |
               |--------------------|
               | alignment pad?     |
               \--------------------/

 

 

              
适使chunk通过chunk map记录里有着page状态一样, run通过当header后挂载
bitmap来记录其中间的region状态. bitmap之后是regions区域. 内部region
大大小小相当于, 且在上下都生redzone保护(需要以装里打开redzone选项).

              
正巧使chunk通过chunk map记录里有page状态一样, run通过当header后挂载
bitmap来记录其内部的region状态. bitmap之后是regions区域. 内部region
大小等, 且在上下都发出redzone保护(需要在设置里打开redzone选项).

大概的话, run就是通过查询bitmap来找到可用的region.
而传统分配器由于用
boundary tag, 空闲region一般是深受夹通往链表管理之. 相比之下, 传统方法查找
速又快, 也重简单. 缺点之前为涉及了, 安全与平静都是缺陷. 从这一点
可观看, Je在统筹思路及以bookkeeping和user
memory分离是贯通始终的原则,
重新特别于对性的影响(事实上这点影响在起条件下为大大赚回了).

简单来说, run就是通过查询bitmap来找到可用之region.
而传统分配器由于采用
boundary tag, 空闲region一般是受夹奔链表管理的. 相比之下, 传统办法查找
快更快, 也又简单. 缺点之前为事关过, 安全及长治久安都是缺陷. 从这一点
可以看, Je在计划思路及拿bookkeeping和user
memory分离是贯通始终的原则,
又要命为对性能的震慑(事实上这点影响于出现条件下于大大赚回来了).

——[ 2.4.2 – size classes

——[ 2.4.2 – size classes

内存分配器对内部管理的region往往以某种特殊规律来分配.
比如Dl将内存划分成
small和large两种植类型.
small类型从8字节始每8独字节为一个区划直至256字节.
假设large类型则由256字节上马, 挂载至dst上.
这种分方式促进分配器对内存进行
有效的管住及操纵, 让曾经分配的内存更加紧实(tightly packed),
以降低外部碎片率.

内存分配器对内部管理的region往往遵照某种特殊规律来分配.
比如Dl将内存划分成
small和large两栽类型.
small类型从8字节开每8单字节为一个分直至256配节.
倘large类型则于256字节始于, 挂载到dst上.
这种划分方式推动分配器对内存进行
得力的管住暨决定, 让一度分配的内存更加紧实(tightly packed),
以降外部碎片率.

Je进一步优化了分红效率. 采用了看似于”二分叉伙伴(Binary
Buddy)算法”的分红方式.
每当Je中将不同尺寸的门类称为size class.

Je进一步优化了分红效率. 采用了看似于”二瓜分伙伴(Binary
Buddy)算法”的分红方式.
在Je中将不同大小的种称为size class.

在Je源码的size_classes.h文件中, 定义了不同系统架构下的region size.
该文件
事实上是由此叫吧size_classes.sh的shell script自动生成的.
script按照四种不同
量纲定义来分各个系平台的别, 然后以它做排列组合,
就可以兼容各个体系.
立四种植量纲分别是,

在Je源码的size_classes.h文件被, 定义了不同系统架构下的region size.
该公文
实在是通过叫吧size_classes.sh的shell script自动生成的.
script按照四种植不同
量纲定义来分别各个系统平台的分别, 然后将它们做排列组合,
就足以配合各个体系.
当下四栽量纲分别是,

LG_SIZEOF_PTR: 代表指针长度, ILP32下蛋是2, LP64则是3.

LG_SIZEOF_PTR: 代表指针长度, ILP32产是2, LP64则是3.

LG_QUANTUM: 量子, binary buddy分得的极度小单位. 除了tiny size,
其他的size
            classes都是quantum的平头倍增大小.

LG_QUANTUM: 量子, binary buddy分得的最好小单位. 除了tiny size,
其他的size
            classes都是quantum的平头倍增大小.

LG_TINY_MIN: 是比quantum更小之size class, 且必须对同步到2的指数倍上.
它是Je可
             分配的极其小之size class.

LG_TINY_MIN: 是比quantum更有些的size class, 且必须对同到2之指数倍上.
它是Je可
             分配的最好小的size class.

LG_PAGE: 就是page大小

LG_PAGE: 就是page大小

据悉binary buddy算法, Je会将内存不断的二平分, 每一样客称作一个group.
同一个
group内又做四等分. 例如, 一个突出的ILP32, tiny等于8byte,
quantum为16byte,
page为4096byte的体系, 其size classes划分是如此的,

基于binary buddy算法, Je会将内存不断的二平分, 每一样客称作一个group.
同一个
group内又做四等分. 例如, 一个杰出的ILP32, tiny等于8byte,
quantum为16byte,
page为4096byte的网, 其size classes划分是这样的,

#if (LG_SIZEOF_PTR == 2 && LG_TINY_MIN == 3 && LG_QUANTUM == 4 && LG_PAGE == 12)
#define    SIZE_CLASSES \
      index, lg_grp, lg_delta, ndelta,  bin, lg_delta_lookup  \
    SC(  0,      3,        3,      0,   yes,        3) \        
                                                       \
    SC(  1,      3,        3,      1,   yes,        3) \        
    SC(  2,      4,        4,      1,   yes,        4) \        
    SC(  3,      4,        4,      2,   yes,        4) \        
    SC(  4,      4,        4,      3,   yes,        4) \        
                                                       \
    SC(  5,      6,        4,      1,   yes,        4) \        
    SC(  6,      6,        4,      2,   yes,        4) \        
    SC(  7,      6,        4,      3,   yes,        4) \        
    SC(  8,      6,        4,      4,   yes,        4) \        
                                                       \
    SC(  9,      7,        5,      1,   yes,        5) \        
    SC( 10,      7,        5,      2,   yes,        5) \        
    SC( 11,      7,        5,      3,   yes,        5) \        
    SC( 12,      7,        5,      4,   yes,        5) \        

    ... ...
#if (LG_SIZEOF_PTR == 2 && LG_TINY_MIN == 3 && LG_QUANTUM == 4 && LG_PAGE == 12)
#define    SIZE_CLASSES \
      index, lg_grp, lg_delta, ndelta,  bin, lg_delta_lookup  \
    SC(  0,      3,        3,      0,   yes,        3) \        
                                                       \
    SC(  1,      3,        3,      1,   yes,        3) \        
    SC(  2,      4,        4,      1,   yes,        4) \        
    SC(  3,      4,        4,      2,   yes,        4) \        
    SC(  4,      4,        4,      3,   yes,        4) \        
                                                       \
    SC(  5,      6,        4,      1,   yes,        4) \        
    SC(  6,      6,        4,      2,   yes,        4) \        
    SC(  7,      6,        4,      3,   yes,        4) \        
    SC(  8,      6,        4,      4,   yes,        4) \        
                                                       \
    SC(  9,      7,        5,      1,   yes,        5) \        
    SC( 10,      7,        5,      2,   yes,        5) \        
    SC( 11,      7,        5,      3,   yes,        5) \        
    SC( 12,      7,        5,      4,   yes,        5) \        

    ... ...

 

 

宏SIZE_CLASSES主要功能就是是得变更几种档次的table.
而SC则根据不同之事态
受定义成不同的含义. SC传入的6独参数的含义如下,

宏SIZE_CLASSES主要功用就是好转变几种类型的table.
而SC则因不同之动静
吃定义成不同的含义. SC传入的6单参数的义如下,

index:      在table中的职务
lg_grp:     所在group的指数
lg_delta:   group内偏移量指数
ndelta:     group内偏移数
bin:        是否由bin记录. small region是记录在bins中
lg_delta_lookup:    在lookup table中的调用S2B_#的奇后缀

index:      在table中之职
lg_grp:     所在group的指数
lg_delta:   group内偏移量指数
ndelta:     group内偏移数
bin:        是否由bin记录. small region是记录在bins中
lg_delta_lookup:    在lookup table中的调用S2B_#的奇后缀

故获reg_size的计算公式, reg_size = 1 << lg_grp + ndelta
<< lg_delta
据悉拖欠公式, 可以落region的克,

于是赢得reg_size的计算公式, reg_size = 1 << lg_grp + ndelta
<< lg_delta
基于该公式, 可以得到region的限量,

       ┌─────────┬─────────┬───────────────────────────────────────┐
       │Category │ Spacing │ Size                                  │
       ├─────────┼─────────┼───────────────────────────────────────┤
       │         │      lg │ [8]                                   │
       │         ├─────────┼───────────────────────────────────────┤
       │         │      16 │ [16, 32, 48, ..., 128]                │
       │         ├─────────┼───────────────────────────────────────┤
       │         │      32 │ [160, 192, 224, 256]                  │
       │         ├─────────┼───────────────────────────────────────┤
       │Small    │      64 │ [320, 384, 448, 512]                  │
       │         ├─────────┼───────────────────────────────────────┤
       │         │     128 │ [640, 768, 896, 1024]                 │
       │         ├─────────┼───────────────────────────────────────┤
       │         │     256 │ [1280, 1536, 1792, 2048]              │
       │         ├─────────┼───────────────────────────────────────┤
       │         │     512 │ [2560, 3072, 3584]                    │
       ├─────────┼─────────┼───────────────────────────────────────┤
       │Large    │   4 KiB │ [4 KiB, 8 KiB, 12 KiB, ..., 4072 KiB] │
       ├─────────┼─────────┼───────────────────────────────────────┤
       │Huge     │   4 MiB │ [4 MiB, 8 MiB, 12 MiB, ...]           │
       └─────────┴─────────┴───────────────────────────────────────┘
       ┌─────────┬─────────┬───────────────────────────────────────┐
       │Category │ Spacing │ Size                                  │
       ├─────────┼─────────┼───────────────────────────────────────┤
       │         │      lg │ [8]                                   │
       │         ├─────────┼───────────────────────────────────────┤
       │         │      16 │ [16, 32, 48, ..., 128]                │
       │         ├─────────┼───────────────────────────────────────┤
       │         │      32 │ [160, 192, 224, 256]                  │
       │         ├─────────┼───────────────────────────────────────┤
       │Small    │      64 │ [320, 384, 448, 512]                  │
       │         ├─────────┼───────────────────────────────────────┤
       │         │     128 │ [640, 768, 896, 1024]                 │
       │         ├─────────┼───────────────────────────────────────┤
       │         │     256 │ [1280, 1536, 1792, 2048]              │
       │         ├─────────┼───────────────────────────────────────┤
       │         │     512 │ [2560, 3072, 3584]                    │
       ├─────────┼─────────┼───────────────────────────────────────┤
       │Large    │   4 KiB │ [4 KiB, 8 KiB, 12 KiB, ..., 4072 KiB] │
       ├─────────┼─────────┼───────────────────────────────────────┤
       │Huge     │   4 MiB │ [4 MiB, 8 MiB, 12 MiB, ...]           │
       └─────────┴─────────┴───────────────────────────────────────┘

 

 

除外, 在size_classes.h中尚定义了片常量,

而外, 在size_classes.h中尚定义了一些常量,

tiny bins的数量
#define    NTBINS            1

tiny bins的数量
#define    NTBINS            1

可以通过lookup table查询的bins数量
#define    NLBINS            29

得经过lookup table查询的bins数量
#define    NLBINS            29

small bins的数量
#define    NBINS            28

small bins的数量
#define    NBINS            28

最大tiny size class的指数
#define    LG_TINY_MAXCLASS    3

最大tiny size class的指数
#define    LG_TINY_MAXCLASS    3

最大lookup size class, 也就是NLBINS – 1个bins
#define    LOOKUP_MAXCLASS        ((((size_t)1) << 11) +
(((size_t)4) << 9))

最大lookup size class, 也就是NLBINS – 1个bins
#define    LOOKUP_MAXCLASS        ((((size_t)1) << 11) +
(((size_t)4) << 9))

最大small size class, 也就是NBINS – 1个bins
#define    SMALL_MAXCLASS        ((((size_t)1) << 11) +
(((size_t)3) << 9))

最大small size class, 也就是NBINS – 1个bins
#define    SMALL_MAXCLASS        ((((size_t)1) << 11) +
(((size_t)3) << 9))

——[ 2.4.3 – size2bin/bin2size

——[ 2.4.3 – size2bin/bin2size

通过SIZE_CLASSES建立的table就是为了当O(1)的光阴复杂度内迅速进行size2bin或
bin2size切换. 同样的技巧于Dl中持有体现, 来看Je是什么促成的.

通过SIZE_CLASSES建立之table就是以以O(1)的时复杂度内高速开展size2bin还是
bin2size切换. 同样的艺以Dl中有体现, 来看Je是怎样兑现之.

size2bin切换提供了零星种植方式, 较快的凡由此查询lookup table,
较迟缓的是计算得到.
从常理及, 只来small size class需要查找bins, 但可透过lookup查询的size
class
数据而小于整个small size class数量. 因此, 部分size class只能算得到.
当原始Je中集合就利用查表法, 但在android版本中或是考虑减多少lookup
table
size, 而增加了直接计算法.

size2bin切换提供了片栽艺术, 较快的凡由此询问lookup table,
较缓慢的是计算得到.
自从常理及, 只来small size class需要查找bins, 但可经lookup查询的size
class
多少要低于整个small size class数量. 因此, 部分size class只能算得到.
于原始Je中集合就以查表法, 但在android版本中恐怕是考虑减多少lookup
table
size, 而增加了直白计算法.

JEMALLOC_ALWAYS_INLINE size_t
small_size2bin(size_t size)
{
    ......
    if (size <= LOOKUP_MAXCLASS)
        return (small_size2bin_lookup(size));
    else
        return (small_size2bin_compute(size));
}
JEMALLOC_ALWAYS_INLINE size_t
small_size2bin(size_t size)
{
    ......
    if (size <= LOOKUP_MAXCLASS)
        return (small_size2bin_lookup(size));
    else
        return (small_size2bin_compute(size));
}

 

 

小于LOOKUP_MAXCLASS的要通过small_size2bin_lookup直接翻表.
查询的算法是这样的,

小于LOOKUP_MAXCLASS的乞求通过small_size2bin_lookup直接翻表.
询问的算法是这般的,

size_t ret = ((size_t)(small_size2bin_tab[(size-1) >> LG_TINY_MIN]));
size_t ret = ((size_t)(small_size2bin_tab[(size-1) >> LG_TINY_MIN]));

 

 

也就是说, Je通过一个

也就是说, Je通过一个

 

 

    f(x) = (x - 1) / 2^LG_TINY_MIN
    f(x) = (x - 1) / 2^LG_TINY_MIN

 

 

的易将size映射到lookup
table的应和区域. 这个table在gdb中恐怕是这样的,

的变换将size映射到lookup
table的呼应区域. 这个table在gdb中或者是这般的,

(gdb) p  /d small_size2bin
$6 = {0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10,
      11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14,
      14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16,
      16, 16, 17 <repeats 16 times>, 18 <repeats 16 times>, 19 <repeats 16 times>,
      20 <repeats 16 times>, 21 <repeats 32 times>, 22 <repeats 32 times>,
      23 <repeats 32 times>, 24 <repeats 32 times>, 25 <repeats 64 times>,
      26 <repeats 64 times>, 27 <repeats 64 times>}
(gdb) p  /d small_size2bin
$6 = {0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10,
      11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14,
      14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16,
      16, 16, 17 <repeats 16 times>, 18 <repeats 16 times>, 19 <repeats 16 times>,
      20 <repeats 16 times>, 21 <repeats 32 times>, 22 <repeats 32 times>,
      23 <repeats 32 times>, 24 <repeats 32 times>, 25 <repeats 64 times>,
      26 <repeats 64 times>, 27 <repeats 64 times>}

 

 

该数组的意思和binary buddy算法是如出一辙的. 对应之bin index越强,
其于屡次组中占的
字节数就逾多. 除了0声泪俱下bin之外, 相邻之4个bin属于同一group,
两只group之间距离二加倍,
因而于数组被占的字节数为就算相差2倍. 所以, 上面往往组的group划分如下,

该数组的义和binary buddy算法是一样的. 对应之bin index越强,
其以反复组中占有的
许节数就更为多. 除了0声泪俱下bin之外, 相邻之4个bin属于同一group,
两单group之间相距二倍增,
因而当频繁组中占的字节数也便相差2倍. 所以, 上面往往组的group划分如下,

{0}, {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}, ...
{0}, {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}, ...

 

 

以bin#9呢例, 其所管辖的限定(128, 160], 由于其放在更胜一级group,
因此相比bin#8
于lookup table中大多同加倍的字节数, 假设我们用查询132, 经过映射,

以bin#9呢条例, 其所管辖的限定(128, 160], 由于该在更强一级group,
因此相比bin#8
以lookup table中多同倍之字节数, 假设我们要查询132, 经过映射,

    (132 - 1) >> 3 = 16
    (132 - 1) >> 3 = 16

 

 

这么好迅速获得其所于的bin #9. 如图,

如此可长足获得其所当的bin #9. 如图,

       bin #1     bin #3          132 is HERE!
          |          |                |
          v          v                v
    +----------------------------------------------------------------
    | 0 | 1 | 2 2 | 3 3 | ... | 8 8 | 9 9 9 9 | ... | 16 ... 16 | ...
    +----------------------------------------------------------------
      ^        ^                 ^       ^                ^
      |        |                 |       |                |
   bin #0    bin #2            bin #8  bin #9          bin #16 
       bin #1     bin #3          132 is HERE!
          |          |                |
          v          v                v
    +----------------------------------------------------------------
    | 0 | 1 | 2 2 | 3 3 | ... | 8 8 | 9 9 9 9 | ... | 16 ... 16 | ...
    +----------------------------------------------------------------
      ^        ^                 ^       ^                ^
      |        |                 |       |                |
   bin #0    bin #2            bin #8  bin #9          bin #16 

 

 

Je巧妙的经过前介绍CLASS_SIZE宏生成了这lookup table, 代码如下,

Je巧妙的经过前介绍CLASS_SIZE宏生成了之lookup table, 代码如下,

JEMALLOC_ALIGNED(CACHELINE)
const uint8_t    small_size2bin_tab[] = {
#define    S2B_3(i)    i,
#define    S2B_4(i)    S2B_3(i) S2B_3(i)
#define    S2B_5(i)    S2B_4(i) S2B_4(i)
#define    S2B_6(i)    S2B_5(i) S2B_5(i)
#define    S2B_7(i)    S2B_6(i) S2B_6(i)
#define    S2B_8(i)    S2B_7(i) S2B_7(i)
#define    S2B_9(i)    S2B_8(i) S2B_8(i)
#define    S2B_no(i)
#define    SC(index, lg_grp, lg_delta, ndelta, bin, lg_delta_lookup) \
    S2B_##lg_delta_lookup(index)
    SIZE_CLASSES
#undef S2B_3
#undef S2B_4
#undef S2B_5
#undef S2B_6
#undef S2B_7
#undef S2B_8
#undef S2B_9
#undef S2B_no
#undef SC
};
JEMALLOC_ALIGNED(CACHELINE)
const uint8_t    small_size2bin_tab[] = {
#define    S2B_3(i)    i,
#define    S2B_4(i)    S2B_3(i) S2B_3(i)
#define    S2B_5(i)    S2B_4(i) S2B_4(i)
#define    S2B_6(i)    S2B_5(i) S2B_5(i)
#define    S2B_7(i)    S2B_6(i) S2B_6(i)
#define    S2B_8(i)    S2B_7(i) S2B_7(i)
#define    S2B_9(i)    S2B_8(i) S2B_8(i)
#define    S2B_no(i)
#define    SC(index, lg_grp, lg_delta, ndelta, bin, lg_delta_lookup) \
    S2B_##lg_delta_lookup(index)
    SIZE_CLASSES
#undef S2B_3
#undef S2B_4
#undef S2B_5
#undef S2B_6
#undef S2B_7
#undef S2B_8
#undef S2B_9
#undef S2B_no
#undef SC
};

 

 

这里的S2B_xx是一模一样系列宏的嵌套展开, 最终对应的就是是见仁见智group在lookup
table中
占的字节数以及bin索引. 相信看懂了面前的牵线就不难理解.

这里的S2B_xx是一致系列宏的嵌套展开, 最终对应之就算是差group在lookup
table中
占的字节数以及bin索引. 相信看懂了前的介绍就不难理解.

一头, 大于LOOKUP_MAXCLASS但小于SMALL_MAXCLASS的size
class不可知查表获得,
急需开展计算. 简言之, 一个bin number是三片段组成的,

单向, 大于LOOKUP_MAXCLASS但小于SMALL_MAXCLASS的size
class不克查表获得,
要展开计算. 简言之, 一个bin number是三有组成的,

bin_number = NTBIN + group_number << LG_SIZE_CLASS_GROUP + mod
bin_number = NTBIN + group_number << LG_SIZE_CLASS_GROUP + mod

 

 

 

 

不怕tiny
bin数量增长该所当group再长group中的晃动(0-2). 源码如下,

就tiny
bin数量增长该所于group再长group中之摆(0-2). 源码如下,

JEMALLOC_INLINE size_t
small_size2bin_compute(size_t size)
{
    ......
    {
        // xf: lg_floor相当于ffs
        size_t x = lg_floor((size<<1)-1);

        // xf: 计算size class所在group number
        size_t shift = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM) ? 0 :
            x - (LG_SIZE_CLASS_GROUP + LG_QUANTUM);
        size_t grp = shift << LG_SIZE_CLASS_GROUP;

        size_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM + 1)
            ? LG_QUANTUM : x - LG_SIZE_CLASS_GROUP - 1;

        size_t delta_inverse_mask = ZI(-1) << lg_delta;
        // xf: 计算剩余mod部分
        size_t mod = ((((size-1) & delta_inverse_mask) >> lg_delta)) &
            ((ZU(1) << LG_SIZE_CLASS_GROUP) - 1);

        // xf: 组合计算bin number
        size_t bin = NTBINS + grp + mod;
        return (bin);
    }
}
JEMALLOC_INLINE size_t
small_size2bin_compute(size_t size)
{
    ......
    {
        // xf: lg_floor相当于ffs
        size_t x = lg_floor((size<<1)-1);

        // xf: 计算size class所在group number
        size_t shift = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM) ? 0 :
            x - (LG_SIZE_CLASS_GROUP + LG_QUANTUM);
        size_t grp = shift << LG_SIZE_CLASS_GROUP;

        size_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM + 1)
            ? LG_QUANTUM : x - LG_SIZE_CLASS_GROUP - 1;

        size_t delta_inverse_mask = ZI(-1) << lg_delta;
        // xf: 计算剩余mod部分
        size_t mod = ((((size-1) & delta_inverse_mask) >> lg_delta)) &
            ((ZU(1) << LG_SIZE_CLASS_GROUP) - 1);

        // xf: 组合计算bin number
        size_t bin = NTBINS + grp + mod;
        return (bin);
    }
}

 

 

其中LG_SIZE_CLASS_GROUP是size_classes.h中之定值,
代表一个group中带有的bin
多少, 根据binary buddy算法, 该值通常状态下是2.
倘要找到size class所当的group, 与那高有效位相关.
Je通过类似于ffs的函数
第一获得size的万丈有效位x,

其中LG_SIZE_CLASS_GROUP是size_classes.h中的定值,
代表一个group中含的bin
数码, 根据binary buddy算法, 该值通常情况下是2.
倘要找到size class所当的group, 与那最高有效位相关.
Je通过类似于ffs的函数
先是得size的最高有效位x,

    size_t x = lg_floor((size<<1)-1);
    size_t x = lg_floor((size<<1)-1);

 

 

至于group number, 则与quantum size有关. 因为除开tiny class, quantum
size
位于group #0的率先个. 因此好推出,

至于group number, 则与quantum size有关. 因为除去tiny class, quantum
size
位于group #0之率先个. 因此好推出,

    group_number = 2^x / quantum_size / 2^LG_SIZE_CLASS_GROUP
    group_number = 2^x / quantum_size / 2^LG_SIZE_CLASS_GROUP

 

 

本着承诺代码就是,

针对许代码就是,

    size_t shift = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM) ? 0 :
            x - (LG_SIZE_CLASS_GROUP + LG_QUANTUM);
    size_t shift = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM) ? 0 :
            x - (LG_SIZE_CLASS_GROUP + LG_QUANTUM);

 

 

假定对诺group起始位置就是是,

假使针对许group起始位置就是,

    size_t grp = shift << LG_SIZE_CLASS_GROUP;
    size_t grp = shift << LG_SIZE_CLASS_GROUP;

 

 

有关mod部分, 与之休戚相关的是高有效位后的一定量独bit.
Je于这边经过了复杂的各项变换,

关于mod部分, 与的有关的凡最高有效位后的鲜单bit.
Je在这里经过了复杂的各项变换,

size_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM + 1)
    ? LG_QUANTUM : x - LG_SIZE_CLASS_GROUP - 1;
size_t delta_inverse_mask = ZI(-1) << lg_delta;
size_t mod = ((((size-1) & delta_inverse_mask) >> lg_delta)) &
    ((ZU(1) << LG_SIZE_CLASS_GROUP) - 1);
size_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM + 1)
    ? LG_QUANTUM : x - LG_SIZE_CLASS_GROUP - 1;
size_t delta_inverse_mask = ZI(-1) << lg_delta;
size_t mod = ((((size-1) & delta_inverse_mask) >> lg_delta)) &
    ((ZU(1) << LG_SIZE_CLASS_GROUP) - 1);

 

 

地方代码直白的翻, 实际上就是要求得如下两单bit,

面代码直白的翻译, 实际上即便要求得如下两个bit,

                        1 0000
                       10 0000
                       11 0000
group #0              100 0000
-------------------------------------------------
                                      +--+
                      101 0000 - 1 = 1|00| 1111
                      110 0000 - 1 = 1|01| 1111
                      111 0000 - 1 = 1|10| 1111
group #1             1000 0000 - 1 = 1|11| 1111
                                      +--+
--------------------------------------------------                                         
                                      +--+
                     1010 0000 - 1 = 1|00|1 1111    
                     1100 0000 - 1 = 1|01|1 1111
                     1110 0000 - 1 = 1|10|1 1111
group #2            10000 0000 - 1 = 1|11|1 1111
                                      +--+
--------------------------------------------------
                        1 0000
                       10 0000
                       11 0000
group #0              100 0000
-------------------------------------------------
                                      +--+
                      101 0000 - 1 = 1|00| 1111
                      110 0000 - 1 = 1|01| 1111
                      111 0000 - 1 = 1|10| 1111
group #1             1000 0000 - 1 = 1|11| 1111
                                      +--+
--------------------------------------------------                                         
                                      +--+
                     1010 0000 - 1 = 1|00|1 1111    
                     1100 0000 - 1 = 1|01|1 1111
                     1110 0000 - 1 = 1|10|1 1111
group #2            10000 0000 - 1 = 1|11|1 1111
                                      +--+
--------------------------------------------------

 

 

基于这个图示再去看Je的代码就不难理解了. mod的算计结果就从0-3之数值.

根据这个图示再失去看Je的代码就不难理解了. mod的计结果虽从0-3底数值.

假定结尾之结果是前三部分的做即,

要是最终之结果是前方三有的的结缘即,

size_t bin = NTBINS + grp + mod;
size_t bin = NTBINS + grp + mod;

 

 

而bin2size查询就大概得多. 上一致节约介绍SIZE_CLASSES时涉嫌过small
region的精打细算
公式, 只需要根据该公式提前计算起所有bin对应之region size,
直接查表即可.
此地不再赘述.

而bin2size询问就概括得多. 上一致节省介绍SIZE_CLASSES时提到过small
region的精打细算
公式, 只需要基于该公式提前计算产生所有bin对应之region size,
直接查表即可.
此处不再赘述.

—-[ 2.5 – bins (arena_bin_t)

—-[ 2.5 – bins (arena_bin_t)

run是分配的实施者, 而分配的调度者是bin. 这个概念同Dl中的bin是相仿的,
但Je中
bin要还复杂一些. 直白地说, 可以将bin看作non-full run的库房,
bin负责记录时
arena中之一一个size class范围外有所non-full run的动情况.
当有分配要时,
arena查找相应size class的bin, 找有可用于分配的run, 再由run分配region.
当然,
为只有small region分配需run, 所以bin也仅对应small size class.

run是分配的执行者, 而分配的调度者是bin. 这个定义同Dl中的bin是近似之,
但Je中
bin要重扑朔迷离一些. 直白地说, 可以管bin看作non-full run的库房,
bin负责记录时
arena中某个一个size class范围外有着non-full run的采取情况.
当起分红要时,
arena查找相应size class的bin, 找有而用来分配的run, 再由run分配region.
当然,
坐只有small region分配要run, 所以bin也唯有对应small size class.

与bin相关的数据结构主要有零星只,
分别是arena_bin_t和arena_bin_info_t.
在2.1.3中提到arena_t内部保留了一个bin数组,
其中的分子就是arena_bin_t.

与bin相关的数据结构主要出三三两两独,
分别是arena_bin_t和arena_bin_info_t.
在2.1.3中提到arena_t内部保留了一个bin数组,
其中的积极分子就arena_bin_t.

夫结构如下,

其二组织如下,

 

 

struct arena_bin_s {
    malloc_mutex_t        lock;    
    arena_run_t            *runcur;
    arena_run_tree_t    runs;
    malloc_bin_stats_t  stats;
};
struct arena_bin_s {
    malloc_mutex_t        lock;    
    arena_run_t            *runcur;
    arena_run_tree_t    runs;
    malloc_bin_stats_t  stats;
};

 

 

lock: 该lock同arena内部的lock不同, 主要担负保护current run.
而对run本身的
      分配和放要需要依赖arena lock. 通常状态下, 获得bin
lock的前提是得
      arena lock, 但反的差立.
      
runcur: 当前而用来分配的run, 一般景象下靠为地方最低的non-full run,
同一时间
        一个bin只来一个current run用于分配.

lock: 该lock同arena内部的lock不同, 主要承担保护current run.
而对run本身的
      分配与放要用依赖arena lock. 通常情况下, 获得bin
lock的前提是沾
      arena lock, 但反的差立.
      
runcur: 当前可是用以分配的run, 一般景象下靠于地方最低的non-full run,
同一时间
        一个bin只发生一个current run用于分配.

runs: rb tree, 记录时arena中该bin对应size class的富有non-full runs.
因为
      分配是通过current run完成的, 所以也一定给current run的仓库.

runs: rb tree, 记录时arena中该bin对应size class的有着non-full runs.
因为
      分配是通过current run完成的, 所以也相当给current run的仓库.

stats: 统计信息.

stats: 统计信息.

别一个与bin相关的构造是arena_bin_info_t. 与前者不同,
bin_info保存的凡
arena_bin_t的静态信息, 包括相对应size class run的bitmap offset, region
size,
region number, bitmap info等等(此类消息一旦class size决定, 就固定下来).
所有
上述消息以Je中出于全局数组arena_bin_info记录. 因此与arena无关,
全局仅保留一份.

另外一个与bin相关的布局是arena_bin_info_t. 与前者不同,
bin_info保存的是
arena_bin_t的静态信息, 包括相对应size class run的bitmap offset, region
size,
region number, bitmap info等等(此类消息如果class size决定, 就固定下来).
所有
上述消息于Je中由全局数组arena_bin_info记录. 因此和arena无关,
全局仅保留一份.

arena_bin_info_t的概念如下,

arena_bin_info_t的定义如下,

struct arena_bin_info_s {
    size_t        reg_size;
    size_t        redzone_size;
    size_t        reg_interval;
    size_t        run_size;
    uint32_t    nregs;
    uint32_t    bitmap_offset;
    bitmap_info_t    bitmap_info;
    uint32_t    reg0_offset;
};
struct arena_bin_info_s {
    size_t        reg_size;
    size_t        redzone_size;
    size_t        reg_interval;
    size_t        run_size;
    uint32_t    nregs;
    uint32_t    bitmap_offset;
    bitmap_info_t    bitmap_info;
    uint32_t    reg0_offset;
};

 

 

reg_size: 与眼前bin的size class相关联的region size.

reg_size: 与当下bin的size class相关联的region size.

reg_interval: reg_size+redzone_size

reg_interval: reg_size+redzone_size

run_size: 当前bin的size class相关联的run size.

run_size: 当前bin的size class相关联的run size.

nregs: 当前bin的size class相关联的run中region数量.

nregs: 当前bin的size class相关联的run中region数量.

bitmap_offset: 当前bin的size class相关联的run中bitmap偏移.

bitmap_offset: 当前bin的size class相关联的run中bitmap偏移.

bitmap_info: 记录时bin的size class相关联的run中bitmap信息.

bitmap_info: 记录时bin的size class相关联的run中bitmap信息.

reg0_offset: index为0之region在run中的偏移量.

reg0_offset: index为0的region在run中之偏移量.

如上记录的静态信息遭受越来越关键之是bitmap_info和bitmap_offset.

以上记录之静态信息被更重大的凡bitmap_info和bitmap_offset.

其中bitmap_info_t定义如下,

其中bitmap_info_t定义如下,

struct bitmap_info_s {
    size_t nbits;
    unsigned nlevels;
    bitmap_level_t levels[BITMAP_MAX_LEVELS+1];
};
struct bitmap_info_s {
    size_t nbits;
    unsigned nlevels;
    bitmap_level_t levels[BITMAP_MAX_LEVELS+1];
};

 

 

nbits: bitmap中逻辑bit位数量(特指level#0的bit数)

nbits: bitmap中逻辑bit位数量(特指level#0的bit数)

nlevels: bitmap的level数量

nlevels: bitmap的level数量

levels: level偏移量数组, 每一样项记录该级level在bitmap中的起始index

levels: level偏移量数组, 每一样起记录该级level在bitmap中之起始index

struct bitmap_level_s {
    size_t group_offset;
};
struct bitmap_level_s {
    size_t group_offset;
};

 

 

在2.3.1节被介绍arena_run_t时既提到Je通过外挂bitmap将bookkeeping和user
memory
分离. 但bitmap查询速度要慢于boundary tag. 为了弥补这个毛病,
Je对这做了改善,
经多级level缓冲以替代线性查找.

以2.3.1节约中牵线arena_run_t时已涉及Je通过外挂bitmap将bookkeeping和user
memory
分离. 但bitmap查询速度而慢于boundary tag. 为了弥补这毛病,
Je对是开了改善,
由此多级level缓冲以替代线性查找.

Je为bitmap增加了多级level, bottom level同普通bitmap一致,
每1bit象征一个region.
假如大一级level中1bit意味着前一级level中一个byte. 譬如说,
若我们于时下run中设有128
个region, 则以ILP32系统上, 需要128/32 = 4byte来代表马上128只region.
Je将即时4独byte
看作level #0. 为了更表示这4单字节是否被占用,
又格外要1byte缘缓存这4byte
的情(仅以了4bit), 此为level#1. 即整个bitmap, 一共有2级level,
共5byte来描述.

Je为bitmap增加了多级level, bottom level同普通bitmap一致,
每1bit代表一个region.
使青出于蓝一级level中1bit意味前一级level中一个byte. 譬如说,
若我们于眼前run中是128
个region, 则以ILP32网上, 需要128/32 = 4byte来代表即128只region.
Je将即刻4只byte
看作level #0. 为了更加表示马上4个字节是否被占用,
又格外要1byte以缓存这4byte
的情节(仅使用了4bit), 此为level#1. 即整个bitmap, 一共有2级level,
共5byte来描述.

                  +--------------+              +--------+
      +-----------|------------ +|   +----------|-------+|
      v           v             ||   v          v       ||
+--------------------------------------------------------------------------
| 1101 0010 | 0000 0000 | ... | 10?? ???? | ???? ???? | 1??? ????    | ...
+--------------------------------------------------------------------------
|<--------- level #0 -------->|<----- level #1 ------>|<- level #2 ->|
                  +--------------+              +--------+
      +-----------|------------ +|   +----------|-------+|
      v           v             ||   v          v       ||
+--------------------------------------------------------------------------
| 1101 0010 | 0000 0000 | ... | 10?? ???? | ???? ???? | 1??? ????    | ...
+--------------------------------------------------------------------------
|<--------- level #0 -------->|<----- level #1 ------>|<- level #2 ->|

 

 

—-[ 2.6 – Thread caches (tcache_t)

—-[ 2.6 – Thread caches (tcache_t)

TLS/TSD是其他一样栽对多线程优化利用的分配技术, Je中谓tcache.
tcache解决
的是同一cpu core下殊线程对heap的竞争.
通过为每个线程指定专属分配区域,
来减小线程间的干扰. 但众所周知这种方式会附加整体内存消耗量.
为了削减副作用,
je将tcache设计改为一个bookkeeping结构,
在tcache中保存之只是恃于外部region
的指针, region对象依旧位居各个run当中. 换句话说,
如果一个region被tcache
笔录了, 那么从run的角度看, 它就曾让分配了.

TLS/TSD是别一样种植对多线程优化利用的分红技术, Je中叫tcache.
tcache解决
的凡同一cpu core下不同线程对heap的竞争.
通过也每个线程指定专属分配区域,
来削弱小线程间的干扰. 但有目共睹这种艺术会叠加整体内存消耗量.
为了减小副作用,
je将tcache设计成为一个bookkeeping结构,
在tcache中保留的只是赖为外部region
的指针, region对象依然在各个run当中. 换句话说,
如果一个region被tcache
笔录了, 那么由run的角度看, 它就是已于分配了.

tcache的情如下,

tcache的始末如下,

struct tcache_s {
    ql_elm(tcache_t) link;        
    uint64_t         prof_accumbytes;
    arena_t             *arena;        
    unsigned         ev_cnt;        
    unsigned         next_gc_bin;    
    tcache_bin_t     tbins[1];    
};
struct tcache_s {
    ql_elm(tcache_t) link;        
    uint64_t         prof_accumbytes;
    arena_t             *arena;        
    unsigned         ev_cnt;        
    unsigned         next_gc_bin;    
    tcache_bin_t     tbins[1];    
};

 

 

 

 

link:
链接节点, 用于将同一个arena下的备tcache链接起来.

link:
链接节点, 用于将和一个arena下之有着tcache链接起来.

prof_accumbytes: memory profile相关.

prof_accumbytes: memory profile相关.

arena: 该tcache所属的arena指针.

arena: 该tcache所属的arena指针.

ev_cnt: 是tcache内部的一个周期计数器. 每当tcache执行同样次于分配要释放时,
        ev_cnt会记录同一差. 直到周期到来, Je会执行同一次incremental gc.
        这里的gc会清理tcache中多余的region, 将其释放掉. 尽管这不意味着
        着系统内存会获得假释, 但可以解放更多之region交给其他更饥饿的
        线程以分配.
        
next_gc_bin: 指向下同样不善gc的binidx. tcache
gc按照同样周期清理一个bin执行.

ev_cnt: 是tcache内部的一个周期计数器. 每当tcache执行同样次等分配还是自由时,
        ev_cnt会记录同一不成. 直到周期至, Je会执行同一次于incremental gc.
        这里的gc会清理tcache中剩下的region, 将其释放掉. 尽管就不意味着
        着系统内存会获得保释, 但可以解放更多之region交给其他还饥饿的
        线程以分配.
        
next_gc_bin: 指向下一致不良gc的binidx. tcache
gc按照平周期清理一个bin执行.

tbins: tcache bin数组. 同样外挂于tcache后面.

tbins: tcache bin数组. 同样外挂于tcache后面.

同arena bin类似, tcache同样有tcache_bin_t和tcache_bin_info_t.
tcache_bin_t作用类似于arena bin, 但其布局使比较继承者重简单. 准确的游说,
tcache bin并从未分配调度的成效, 而仅从至记录作用. 其里面通过一个stack
记录指向外部arena run中之region指针. 而一旦region被cache到tbins内,
就是非可知再次给另外任何线程所运用, 尽管它们可能还是跟任何线程tcache中记录的
region位于与一个arena run中.

同arena bin类似, tcache同样有tcache_bin_t和tcache_bin_info_t.
tcache_bin_t作用类似于arena bin, 但其布局使于继承者又简单. 准确的游说,
tcache bin并不曾分配调度的效力, 而仅于至记录作用. 其中间通过一个stack
记录指向外部arena run中之region指针. 而一旦region被cache到tbins内,
就是未克再次吃其他任何线程所采取, 尽管她或许还与任何线程tcache中记录之
region位于与一个arena run中.

tcache bin结构如下,

tcache bin结构如下,

struct tcache_bin_s {
    tcache_bin_stats_t tstats;
    int     low_water;
    unsigned    lg_fill_div;
    unsigned    ncached;
    void        **avail;
}
struct tcache_bin_s {
    tcache_bin_stats_t tstats;
    int     low_water;
    unsigned    lg_fill_div;
    unsigned    ncached;
    void        **avail;
}

 

 

 

 

tstats:
tcache bin内部统计.

tstats:
tcache bin内部统计.

low_water: 记录点滴不良gc间tcache内部使用的最低水线.
该数值及生同样不善gc时尝试
           释放的region数量来关. 释放量相当给low water数值的3/4.
           
lg_fill_div: 用作tcache refill时当除数. 当tcache耗尽时, 会请求arena
run
             进行refill. 但refill不见面一次性灌满tcache,
而是依照其极特别容量
             缩小2^lg_fill_div的倍增数. 该数值及low_water一样是动态的,
两者
             互相配合确保tcache处于一个客观的盈度.
             
ncached: 指当前缓存的region数量, 同时为意味着栈顶index.

low_water: 记录点滴不好gc间tcache内部以的最低水线.
该数值与下同样不行gc时尝试
           释放的region数量来关. 释放量相当给low water数值的3/4.
           
lg_fill_div: 用作tcache refill时当除数. 当tcache耗尽时, 会请求arena
run
             进行refill. 但refill不会见一次性灌满tcache,
而是依照其极深容量
             缩小2^lg_fill_div的倍增数. 该数值及low_water一样是动态的,
两者
             互相配合确保tcache处于一个合理之满度.
             
ncached: 指当前缓存的region数量, 同时也代表栈顶index.

avail: 保存region指针的stack, 称为avail-stack.

avail: 保存region指针的stack, 称为avail-stack.

tcache_bin_info_t保存tcache bin的静态信息. 其本人才保留了tcache
max容量.
欠数值是当tcache boot时因相对应之arena bin的nregs决定的.
通常等于nregs
的次倍增, 但不得跨越TCACHE_NSLOTS_SMALL_MAX. 该数值默看200,
但在android
受到大大升级了拖欠限量, small bins不得超过8, large bins则为16.

tcache_bin_info_t保存tcache bin的静态信息. 其本人才保留了tcache
max容量.
拖欠数值是当tcache boot时根据相呼应之arena bin的nregs决定的.
通常等于nregs
的次加倍, 但不得越TCACHE_NSLOTS_SMALL_MAX. 该数值默看200,
但在android
丁大大升级了拖欠限量, small bins不得超过8, large bins则为16.

struct tcache_bin_info_s {
    unsigned    ncached_max;
};
struct tcache_bin_info_s {
    unsigned    ncached_max;
};

 

 

tcache layout如下,

tcache layout如下,

                +---------------+
              / | link          |
   tcache_t  <  | next_gc_bin   |
              \ | ...           |
                |---------------|
              / | tstats        |
   tbins[0]  <  | ...           |
              | | ncached       |
              \ | avail --------------+
                |---------------|     |
                | ...           |     |  
                | ...           |     |  
                | ...           |     |  
                |---------------|     |
              / | tstats        |     |
  tbins[nhb  <  | ...           |     |
     ins]     | | ncached       |     |                   
              \ | avail --------------|---+               
                |---------------|     |   |               current arena run
                | padding       |     |   |               +----------------+      
                |---------------| <---+   |               | run header     |
              / | stack[0]      |         |               | ...            |
avail-stack  <  | stack[1]      |         |               | bitmap         |
for tbins[0]  | | ...           |         |               | ...            |
              | | ...           |         |               |----------------|
              | | stack[ncached |         |               | region #0      |
              \ | _max - 1]     |         |               | ...            |
                |---------------|         |               |----------------|
                | ...           |         |    +--------> | region #1      |
                | ...           |         |    |          | ...            |
                | ...           |         |    |          |----------------|
                |---------------| <-------+    |          | ...            |
avail-stack   / | stack[0]      |--------------|--+       | ...            |
for tbins[   <  | ...           |              |  |       |----------------|
 nhbins]      | | stack[n]      |--------------|--|-----> | region #n      |
              | | ...           |              |  |       | ...            |
              | | stack[ncached |              |  |       |----------------|
              \ | _max - 1]     |--------------+  |       | ...            |
                +---------------+                 |       | ...            |
                                                  |       |----------------|
                                                  +-----> | region #nregs-1|
                                                          | ...            |
                                                          +----------------+
                +---------------+
              / | link          |
   tcache_t  <  | next_gc_bin   |
              \ | ...           |
                |---------------|
              / | tstats        |
   tbins[0]  <  | ...           |
              | | ncached       |
              \ | avail --------------+
                |---------------|     |
                | ...           |     |  
                | ...           |     |  
                | ...           |     |  
                |---------------|     |
              / | tstats        |     |
  tbins[nhb  <  | ...           |     |
     ins]     | | ncached       |     |                   
              \ | avail --------------|---+               
                |---------------|     |   |               current arena run
                | padding       |     |   |               +----------------+      
                |---------------| <---+   |               | run header     |
              / | stack[0]      |         |               | ...            |
avail-stack  <  | stack[1]      |         |               | bitmap         |
for tbins[0]  | | ...           |         |               | ...            |
              | | ...           |         |               |----------------|
              | | stack[ncached |         |               | region #0      |
              \ | _max - 1]     |         |               | ...            |
                |---------------|         |               |----------------|
                | ...           |         |    +--------> | region #1      |
                | ...           |         |    |          | ...            |
                | ...           |         |    |          |----------------|
                |---------------| <-------+    |          | ...            |
avail-stack   / | stack[0]      |--------------|--+       | ...            |
for tbins[   <  | ...           |              |  |       |----------------|
 nhbins]      | | stack[n]      |--------------|--|-----> | region #n      |
              | | ...           |              |  |       | ...            |
              | | stack[ncached |              |  |       |----------------|
              \ | _max - 1]     |--------------+  |       | ...            |
                +---------------+                 |       | ...            |
                                                  |       |----------------|
                                                  +-----> | region #nregs-1|
                                                          | ...            |
                                                          +----------------+

 

 

—-[ 2.7 – Extent Node (extent_node_t)

—-[ 2.7 – Extent Node (extent_node_t)

extent node代表huge region, 即大于chunk大小的内存单元. 同arena
run不同,
extent node并非是一个header构造, 而是外挂的. 因此其自身仍属small
region.
只不过并无经bin分配, 而由base_nodes直接动态创建.

extent node代表huge region, 即大于chunk大小的外存单元. 同arena
run不同,
extent node并非是一个header构造, 而是外挂的. 因此该自身仍属于small
region.
只不过并无通过bin分配, 而由base_nodes直接动态创建.

Je中对负有huge region都是经过rb tree管理. 因此extent node同时为是tree
node.
一个node节点被同时挂载到一定量株rb tree上. 一棵采用szad的询问办法,
另一样蔸则用
纯ad的方式. 作用是当执行chunk recycle时查询及可用region, 后面会详述.

Je中针对持有huge region都是经过rb tree管理. 因此extent node同时为是tree
node.
一个node节点被同时挂载到一定量棵rb tree上. 一蔸采用szad的询问艺术,
另一样株则用
纯ad的方式. 作用是当尽chunk recycle时查询到可用region, 后面会详述.

struct extent_node_s {
    rb_node(extent_node_t)    link_szad;
    rb_node(extent_node_t)    link_ad;
    prof_ctx_t        *prof_ctx;
    void            *addr;
    size_t            size;
    arena_t            *arena;
    bool            zeroed;
};
struct extent_node_s {
    rb_node(extent_node_t)    link_szad;
    rb_node(extent_node_t)    link_ad;
    prof_ctx_t        *prof_ctx;
    void            *addr;
    size_t            size;
    arena_t            *arena;
    bool            zeroed;
};

 

 

link_szad: szad tree的link节点.

link_szad: szad tree的link节点.

link_ad: ad tree的link节点.

link_ad: ad tree的link节点.

prof_ctx: 用于memory profile.

prof_ctx: 用于memory profile.

addr: 指向huge region的指针.

addr: 指向huge region的指针.

size: region size.

size: region size.

arena: huge region所属arena.

arena: huge region所属arena.

zeroed: 代表是否zero-filled, chunk recycle时会见用到.

zeroed: 代表是否zero-filled, chunk recycle时会就此到.

—-[ 2.8 – Base

—-[ 2.8 – Base

base并无是数据类型, 而是一块突出区域, 主要服务被其中meta
data(例如arena_t,
tcache_t, extent_node_t等等)的分配. base区域管理暨如下变量相关,

base并无是数据类型, 而是一片突出区域, 主要劳务为中meta
data(例如arena_t,
tcache_t, extent_node_t等等)的分配. base区域管理暨如下变量相关,

static malloc_mutex_t    base_mtx;
static void        *base_pages;    
static void        *base_next_addr;     
static void        *base_past_addr;
static extent_node_t    *base_nodes;
static malloc_mutex_t    base_mtx;
static void        *base_pages;    
static void        *base_next_addr;     
static void        *base_past_addr;
static extent_node_t    *base_nodes;

 

 

base_mtx:       base lock
base_pages:     base page指针, 代表所有区域的开端位置.
base_next_addr: 当前base指针, 类似于brk指针.
base_past_addr: base page的上限指针.
base_nodes:     指向extent_node_t链表的外挂头指针.

base_mtx:       base lock
base_pages:     base page指针, 代表任何区域的起始位置.
base_next_addr: 当前base指针, 类似于brk指针.
base_past_addr: base page的上限指针.
base_nodes:     指向extent_node_t链表的外挂头指针.

base page源于arena中的空闲chunk, 通常状态下, 大小相当给chunk.
当base耗尽时,
会见盖chunk alloc的名义重新申请新的base pages.  

base page源于arena中之空闲chunk, 通常状态下, 大小相当给chunk.
当base耗尽时,
会见以chunk alloc的名义重新申请新的base pages.  

为确保中meta data的便捷分配与访问.
Je将里面请求大小都针对同到cache-line上,
坐避免在SMP下的false sharing. 而分红方式及,
采用了迅速移动base_next_addr
指南针进行高效开采的方法.

为了保中meta data的快分配和访问.
Je将内请求大小都对准合到cache-line上,
因避免以SMP下的false sharing. 而分红办法及,
采用了快移动base_next_addr
指南针进行快速开采的方法.

void *
base_alloc(size_t size)
{
    ......
    // xf: 将内部分配请求对齐的cache-line上, 阻止false sharing
    csize = CACHELINE_CEILING(size);

    malloc_mutex_lock(&base_mtx);
    // xf: 如果base耗尽, 则重新分配base_pages. 默认大小为chunksize.
    if ((uintptr_t)base_next_addr + csize > (uintptr_t)base_past_addr) {
        if (base_pages_alloc(csize)) {
            malloc_mutex_unlock(&base_mtx);
            return (NULL);
        }
    }
    // xf: 快速向前开采
    ret = base_next_addr;
    base_next_addr = (void *)((uintptr_t)base_next_addr + csize);
    malloc_mutex_unlock(&base_mtx);

    return (ret);
}
void *
base_alloc(size_t size)
{
    ......
    // xf: 将内部分配请求对齐的cache-line上, 阻止false sharing
    csize = CACHELINE_CEILING(size);

    malloc_mutex_lock(&base_mtx);
    // xf: 如果base耗尽, 则重新分配base_pages. 默认大小为chunksize.
    if ((uintptr_t)base_next_addr + csize > (uintptr_t)base_past_addr) {
        if (base_pages_alloc(csize)) {
            malloc_mutex_unlock(&base_mtx);
            return (NULL);
        }
    }
    // xf: 快速向前开采
    ret = base_next_addr;
    base_next_addr = (void *)((uintptr_t)base_next_addr + csize);
    malloc_mutex_unlock(&base_mtx);

    return (ret);
}

 

 

跟平常的base alloc有所不同,
分配extent_node_t会优先从一个node链表中赢得
节点, 而base_nodes则也该链表的外挂头指针. 只有当该耗尽时,
才使用前的
分红方式. 这里分别对待extent_node_t与另外项目, 主要跟chunk
recycle机制
关于, 后面会做详细说明.

及一般的base alloc有所不同,
分配extent_node_t会优先由一个node链表中获得
节点, 而base_nodes则也该链表的外挂头指针. 只有当那耗尽时,
才使用前的
分配方式. 这里分别对待extent_node_t与另类, 主要及chunk
recycle机制
有关, 后面会做详细说明.

有趣的凡, 该链表实际上借用了extent node内部rb tree
node的左子树节点指针
作为其link指针. 如2.7节所述, extent_node_t结构的开场位置存放一个rb
node.
可是每当此间, 当base_nodes赋值给ret后, 会强制将ret转型成(extent_node_t
**),
事实上即便是据向extent_node_t结构体的率先只field的指针,
并将其对的node
指南针记录及base_nodes里, 成为新的header节点.
这里用细致回味是强制类型
改换的高超的处在.

有趣的凡, 该链表实际上借用了extent node内部rb tree
node的左子树节点指针
作为其link指针. 如2.7节所述, extent_node_t结构的苗头位置存放一个rb
node.
然每当这里, 当base_nodes赋值给ret后, 会强制将ret转型成(extent_node_t
**),
骨子里就是凭向extent_node_t结构体的率先只field的指针,
并将那对的node
指南针记录及base_nodes里, 成为新的header节点.
这里用密切回味是强制类型
转换的神妙的处在.

ret = base_nodes
     |
     v   +---- (extent_node_t**)ret
     +---|------------------------------ +
     |   |              extent_node      |
     | +-|-------------------------+     |
     | | v       rb_node           |     |
     | | +----------+-----------+  |     |
     | | | rbn_left | rbn_right |  | ... |
     | | +----------+-----------+  |     |
     | +-------|-------------------+     |
     +---------|-------------------------+
               v
base_nodes---> +---------------+
               | extent_node   |
               +---------------+

extent_node_t *
base_node_alloc(void)
{
    extent_node_t *ret;

    malloc_mutex_lock(&base_mtx);
    if (base_nodes != NULL) {
        ret = base_nodes;
        // xf: 这里也可以理解为, base_nodes = (extent_node_t*)(*ret);
        base_nodes = *(extent_node_t **)ret;
        malloc_mutex_unlock(&base_mtx);
    } else {
        malloc_mutex_unlock(&base_mtx);
        ret = (extent_node_t *)base_alloc(sizeof(extent_node_t));
    }

    return (ret);
}
ret = base_nodes
     |
     v   +---- (extent_node_t**)ret
     +---|------------------------------ +
     |   |              extent_node      |
     | +-|-------------------------+     |
     | | v       rb_node           |     |
     | | +----------+-----------+  |     |
     | | | rbn_left | rbn_right |  | ... |
     | | +----------+-----------+  |     |
     | +-------|-------------------+     |
     +---------|-------------------------+
               v
base_nodes---> +---------------+
               | extent_node   |
               +---------------+

extent_node_t *
base_node_alloc(void)
{
    extent_node_t *ret;

    malloc_mutex_lock(&base_mtx);
    if (base_nodes != NULL) {
        ret = base_nodes;
        // xf: 这里也可以理解为, base_nodes = (extent_node_t*)(*ret);
        base_nodes = *(extent_node_t **)ret;
        malloc_mutex_unlock(&base_mtx);
    } else {
        malloc_mutex_unlock(&base_mtx);
        ret = (extent_node_t *)base_alloc(sizeof(extent_node_t));
    }

    return (ret);
}

 

 

–[ 3 – Allocation

–[ 3 – Allocation

—-[ 3.1 – Overview

—-[ 3.1 – Overview

在2.3.2节省吃获悉, Je将size class划分成small, large, huge三种类型.
分配时
及时三种植档次分别随不同之算法执行. 后面的章节也以仍此类型顺序描述.

当2.3.2节吃获悉, Je将size class划分成small, large, huge三栽类型.
分配时
这三种植类型分别按不同的算法执行. 后面的区块也将按部就班这个类别顺序描述.

圆来说, Je分配函数从je_malloc入口开始, 经过,
je_malloc -> imalloc_body -> imalloc -> imalloct —>
arena_malloc
                                                  |                  
                                                  +-> huge_malloc
事实上执行分配的各自是针对性应small/large的arena malloc和对应huge的huge
malloc.

整体来说, Je分配函数从je_malloc入口开始, 经过,
je_malloc -> imalloc_body -> imalloc -> imalloct —>
arena_malloc
                                                  |                  
                                                  +-> huge_malloc
实质上履行分配的各自是针对性应small/large的arena malloc和针对性应huge的huge
malloc.

分配算法可以包括如下,

分红算法可以概括如下,

  1. 第一检查Je是否初始化, 如果没有则初始化Je,
    并标记全局malloc_initialized标记.
  2. 反省请求size是否高于huge, 如果是则执行8, 否则上下同样步.
  3. 执行arena_malloc, 首先检查size是否低于等于small maxclass,
    如果是虽然生同样步,
       否则实行6.
  4. 要是同意且当前线程已绑定tcache, 则从tcache分配small, 并返回.
    否则生一样步.
  5. choose arena, 并执行arena malloc small, 返回.
  6. 比方同意且当前线程已绑定tcache, 则从tcache分配large, 并返回.
    否则生一样步.
  7. choose arena, 并执行arena malloc large, 返回.
  8. 执行huge malloc, 并返回.
  1. 第一检查Je是否初始化, 如果没有则初始化Je,
    并标记全局malloc_initialized标记.
  2. 自我批评请求size是否超huge, 如果是则执行8, 否则上下一样步.
  3. 执行arena_malloc, 首先检查size是否低于等于small maxclass,
    如果是则生一样步,
       否则履行6.
  4. 只要同意且当前线程已绑定tcache, 则从tcache分配small, 并返回.
    否则生一致步.
  5. choose arena, 并执行arena malloc small, 返回.
  6. 假若允许且当前线程已绑定tcache, 则从tcache分配large, 并返回.
    否则生一致步.
  7. choose arena, 并执行arena malloc large, 返回.
  8. 执行huge malloc, 并返回.

—-[ 3.2 – Initialize

—-[ 3.2 – Initialize

Je通过全局标记malloc_initialized指代是否初始化. 在每次分配时,
需要检查该标记,
假使没则执行malloc_init.

Je通过全局标记malloc_initialized指代是否初始化. 在每次分配时,
需要检讨该标记,
万一无则执行malloc_init.

而通常条件下, malloc_init是在Je库被载入之前就是调用的.
通过gcc的编译扩展属性
“constructor”实现,

但是通常条件下, malloc_init是以Je库被载入之前就是调用的.
通过gcc的编译扩展属性
“constructor”实现,

JEMALLOC_ATTR(constructor)
static void
jemalloc_constructor(void)
{
    malloc_init();
}
JEMALLOC_ATTR(constructor)
static void
jemalloc_constructor(void)
{
    malloc_init();
}

 

 

对接下由malloc_init_hard执行员初始化工作.
这里首先需考虑的凡多线程初始化
造成的重入,
Je通过malloc_initialized和malloc_initializer两独记来识别.

连下由malloc_init_hard执行各项初始化工作.
这里首先要考虑的凡多线程初始化
致的重入,
Je通过malloc_initialized和malloc_initializer两单记号来识别.

malloc_mutex_lock(&init_lock);
// xf: 如果在获得init_lock前已经有其他线程完成malloc_init,
// 或者当前线程在初始化过程中执行了malloc, 导致递归初始化, 则立即退出.
if (malloc_initialized || IS_INITIALIZER) {
    malloc_mutex_unlock(&init_lock);
    return (false);
}
// xf: 如果开启多线程初始化, 需要执行busy wait直到malloc_init在另外线程中
// 执行完毕后返回.
#ifdef JEMALLOC_THREADED_INIT
if (malloc_initializer != NO_INITIALIZER && IS_INITIALIZER == false) {
    do {
        malloc_mutex_unlock(&init_lock);
        CPU_SPINWAIT;
        malloc_mutex_lock(&init_lock);
    } while (malloc_initialized == false);
    malloc_mutex_unlock(&init_lock);
    return (false);
}
#endif
// xf: 将当前线程注册为initializer
malloc_initializer = INITIALIZER;
malloc_mutex_lock(&init_lock);
// xf: 如果在获得init_lock前已经有其他线程完成malloc_init,
// 或者当前线程在初始化过程中执行了malloc, 导致递归初始化, 则立即退出.
if (malloc_initialized || IS_INITIALIZER) {
    malloc_mutex_unlock(&init_lock);
    return (false);
}
// xf: 如果开启多线程初始化, 需要执行busy wait直到malloc_init在另外线程中
// 执行完毕后返回.
#ifdef JEMALLOC_THREADED_INIT
if (malloc_initializer != NO_INITIALIZER && IS_INITIALIZER == false) {
    do {
        malloc_mutex_unlock(&init_lock);
        CPU_SPINWAIT;
        malloc_mutex_lock(&init_lock);
    } while (malloc_initialized == false);
    malloc_mutex_unlock(&init_lock);
    return (false);
}
#endif
// xf: 将当前线程注册为initializer
malloc_initializer = INITIALIZER;

 

 

初始化工作由各个xxx_boot函数完成. 注意的凡,
boot函数返回false代表成功,
不然代表失败.

初始化工作由各个xxx_boot函数完成. 注意的凡,
boot函数返回false代表成功,
不然代表失败.

tsd boot: Thread specific data初始化,
主要承担tsd析构函数屡屡组长度初始化.

tsd boot: Thread specific data初始化,
主要承担tsd析构函数屡组长度初始化.

base boot: base是Je内部用于meta data分配的保留区域,
使用其中独立的分配方式.
           base boot负责base node和base mutex的初始化.
chunk boot: 主要有三桩工作,
            1. 确认chunk_size和chunk_npages
            2. chunk_dss_boot, chunk dss指chunk分配的dss(Data Storage
Segment)
               方式. 其中提到dss_base, dss_prev指针的初始化工作.
            3. chunk tree的初始化, 在chunk recycle时如就此到.
arena boot: 主要是肯定arena_maxclass,
这个size代表arena管理的极酷region,
            超过该值被看huge region.
            在2.2.2小节中有过介绍, 先通过反复迭代计算出map_bias, 再用
            chunksize – (map_bias << LG_PAGE)即可得到.
            另外还对其它一个根本之静态数组arena_bin_info执行了初始化.
可参看
            2.3.2介绍class size的部分.
tcache boot: 分为tcache_boot0和tcache_boot1少于个部分执行.
             前者肩负tcache所有静态信息, 包含tcache_bin_info,
stack_nelms,
             nhbins等之初始化.
             后者负责tcache tsd数据的初始化(tcache保存到线程tsd中).
huge boot: 负责huge mutex和huge tree的初始化.

base boot: base是Je内部用于meta data分配的保留区域,
使用其中独立的分配方式.
           base boot负责base node和base mutex的初始化.
chunk boot: 主要发生三件工作,
            1. 确认chunk_size和chunk_npages
            2. chunk_dss_boot, chunk dss指chunk分配的dss(Data Storage
Segment)
               方式. 其中提到dss_base, dss_prev指针的初始化工作.
            3. chunk tree的初始化, 在chunk recycle时如果就此到.
arena boot: 主要是承认arena_maxclass,
这个size代表arena管理之顶要命region,
            超过该值被认为huge region.
            在2.2.2小节中发出了介绍, 先通过反复迭代计算出map_bias, 再用
            chunksize – (map_bias << LG_PAGE)即可得到.
            另外还针对性其余一个要的静态数组arena_bin_info执行了初始化.
可参照
            2.3.2介绍class size的部分.
tcache boot: 分为tcache_boot0和tcache_boot1少个组成部分执行.
             前者肩负tcache所有静态信息, 包含tcache_bin_info,
stack_nelms,
             nhbins等的初始化.
             后者负责tcache tsd数据的初始化(tcache保存至线程tsd中).
huge boot: 负责huge mutex和huge tree的初始化.

除, 其他关键之初始化还包分配arenas数组.
注意arenas是一个对准指针数组
的指针, 因此各个arena还亟需动态创建. 这里Je采取了lazy create的道,
只有当
choose_arena时才可能鉴于choose_arena_hard创建真实的arena实例.
但当malloc_init
受到, 首只arena还是碰头于这时创立, 以保证中心的分配.

除却, 其他重大的初始化还连分配arenas数组.
注意arenas是一个对指针数组
的指针, 因此各个arena还得动态创建. 这里Je采取了lazy create的方,
只有当
choose_arena时才可能是因为choose_arena_hard创建真实的arena实例.
但于malloc_init
备受, 首独arena还是会以这时候创造, 以保证基本的分配.

相关代码如下,

有关代码如下,

arena_t *init_arenas[1];
......

// xf: 此时narenas_total只有1
narenas_total = narenas_auto = 1;
arenas = init_arenas;
memset(arenas, 0, sizeof(arena_t *) * narenas_auto);

// xf: 创建首个arena实例, 保存到临时数组init_arenas中
arenas_extend(0);
......

// xf: 获得当前系统核心数量
ncpus = malloc_ncpus();
......

// xf: 默认的narenas为核心数量的4倍
if (opt_narenas == 0) {    
    if (ncpus > 1)
        opt_narenas = ncpus << 2;
    else
        opt_narenas = 1;
}

// xf: android中max arenas限制为2, 参考mk文件
#if defined(ANDROID_MAX_ARENAS)
if (opt_narenas > ANDROID_MAX_ARENAS)
    opt_narenas = ANDROID_MAX_ARENAS;
#endif
narenas_auto = opt_narenas;
......

// xf: 修正narenas_total
narenas_total = narenas_auto;

// xf: 根据total数量, 构造arenas数组, 并置空
arenas = (arena_t **)base_alloc(sizeof(arena_t *) * narenas_total);
......
memset(arenas, 0, sizeof(arena_t *) * narenas_total);

// xf: 将之前的首个arena实例指针保存到新构造的arenas数组中
arenas[0] = init_arenas[0];
arena_t *init_arenas[1];
......

// xf: 此时narenas_total只有1
narenas_total = narenas_auto = 1;
arenas = init_arenas;
memset(arenas, 0, sizeof(arena_t *) * narenas_auto);

// xf: 创建首个arena实例, 保存到临时数组init_arenas中
arenas_extend(0);
......

// xf: 获得当前系统核心数量
ncpus = malloc_ncpus();
......

// xf: 默认的narenas为核心数量的4倍
if (opt_narenas == 0) {    
    if (ncpus > 1)
        opt_narenas = ncpus << 2;
    else
        opt_narenas = 1;
}

// xf: android中max arenas限制为2, 参考mk文件
#if defined(ANDROID_MAX_ARENAS)
if (opt_narenas > ANDROID_MAX_ARENAS)
    opt_narenas = ANDROID_MAX_ARENAS;
#endif
narenas_auto = opt_narenas;
......

// xf: 修正narenas_total
narenas_total = narenas_auto;

// xf: 根据total数量, 构造arenas数组, 并置空
arenas = (arena_t **)base_alloc(sizeof(arena_t *) * narenas_total);
......
memset(arenas, 0, sizeof(arena_t *) * narenas_total);

// xf: 将之前的首个arena实例指针保存到新构造的arenas数组中
arenas[0] = init_arenas[0];

 

 

—-[ 3.3 – Small allocation (Arena)

—-[ 3.3 – Small allocation (Arena)

优先介绍最复杂的arena malloc small.

先介绍最复杂的arena malloc small.

  1. 先通过small_size2bin查到bin index(2.4.3节有述).
  2. 万一对应bin中current run可用则进下一样步, 否则实施4.
  3. 由arena_run_reg_alloc在current run中直接分配, 并返回.
  4. current run耗尽或不在, 尝试从bin中落可用run以填充current run,
       成功则执行9, 否则上下一样步.
  5. 现阶段bin的run tree中没可用run,
    转而自从arena的avail-tree上尝切割一个
       可用run, 成功则执行9, 否则跻身下一样步.
  6. 脚下arena没有可用之空闲run, 构造一个新的chunk以分配new run. 成功则
       执行9, 否则进下一致步.
  7. chunk分配失败, 再次查询arena的avail-tree, 查找可用run. 成功则执行9,
       否则进下同样步.
  8. alloc run尝试彻底破产, 则再次询问时bin的run-tree, 尝试获取run.
  9. 在动新取得run之前, 重新检讨时bin的current run, 如果可用(这里发生
       两种可能, 其一是其他线程可能由此free释放了剩下的region或run, 另一样栽
       可能是快在时线程之前已分配了新run), 则使用其分配, 并返回.
       另外, 如果当前手中的new run是空的, 则将那自由掉.
    否则一旦那地点比current
       run更不比, 则交换二吧, 将故的current run插回avail-tree.
  10. 在new run中分配region, 并返回.
  1. 先通过small_size2bin查到bin index(2.4.3节有述).
  2. 要是对应bin中current run可用则进下一样步, 否则履行4.
  3. 由arena_run_reg_alloc在current run中一直分配, 并返回.
  4. current run耗尽或非存在, 尝试从bin中获取可用run以填充current run,
       成功则执行9, 否则跻身下同样步.
  5. 眼前bin的run tree中绝非可用run,
    转而自从arena的avail-tree上尝试切割一个
       可用run, 成功则执行9, 否则进下一致步.
  6. 脚下arena没有可用的空闲run, 构造一个初的chunk以分配new run. 成功则
       执行9, 否则进下同样步.
  7. chunk分配失败, 再次查询arena的avail-tree, 查找可用run. 成功则执行9,
       否则进下同样步.
  8. alloc run尝试彻底破产, 则再次查询时bin的run-tree, 尝试获取run.
  9. 每当用新取得run之前, 重新检查时bin的current run, 如果可用(这里发生
       两种或, 其一是其他线程可能由此free释放了剩余的region或run, 另一样栽
       可能是快当当下线程之前早已分配了新run), 则使用那分配, 并返回.
       另外, 如果当前手中的new run是拖欠的, 则将其放出掉.
    否则一经该地址比current
       run更没有, 则交换二啊, 将故的current run插回avail-tree.
  10. 在new run中分配region, 并返回.
void *
arena_malloc_small(arena_t *arena, size_t size, bool zero)
{
    ......
    // xf: 根据size计算bin index
    binind = small_size2bin(size);
    assert(binind < NBINS);
    bin = &arena->bins[binind];
    size = small_bin2size(binind);

    malloc_mutex_lock(&bin->lock);
    // xf: 如果bin中current run不为空, 且存在空闲region, 则在current
    // run中分配. 否则在其他run中分配.
    if ((run = bin->runcur) != NULL && run->nfree > 0)
        ret = arena_run_reg_alloc(run, &arena_bin_info[binind]);
    else
        ret = arena_bin_malloc_hard(arena, bin);

    // xf: 若返回null, 则分配失败.
    if (ret == NULL) {
        malloc_mutex_unlock(&bin->lock);
        return (NULL);
    }
    ......

    return (ret);
}
void *
arena_malloc_small(arena_t *arena, size_t size, bool zero)
{
    ......
    // xf: 根据size计算bin index
    binind = small_size2bin(size);
    assert(binind < NBINS);
    bin = &arena->bins[binind];
    size = small_bin2size(binind);

    malloc_mutex_lock(&bin->lock);
    // xf: 如果bin中current run不为空, 且存在空闲region, 则在current
    // run中分配. 否则在其他run中分配.
    if ((run = bin->runcur) != NULL && run->nfree > 0)
        ret = arena_run_reg_alloc(run, &arena_bin_info[binind]);
    else
        ret = arena_bin_malloc_hard(arena, bin);

    // xf: 若返回null, 则分配失败.
    if (ret == NULL) {
        malloc_mutex_unlock(&bin->lock);
        return (NULL);
    }
    ......

    return (ret);
}

 

 

——[ 3.3.1 – arena_run_reg_alloc

——[ 3.3.1 – arena_run_reg_alloc

  1. 先是冲bin_info中的静态信息bitmap_offset计算bitmap基址.
  2. 环视时run的bitmap, 获得第一独free region所当的位置.
  3. region地址 = run基址 + 第一单region的偏移量 + free region索引 *
                    region内部size
  1. 率先冲bin_info中的静态信息bitmap_offset计算bitmap基址.
  2. 环顾时run的bitmap, 获得第一单free region所当的位置.
  3. region地址 = run基址 + 第一独region的偏移量 + free region索引 *
                    region内部size
static inline void *
arena_run_reg_alloc(arena_run_t *run, arena_bin_info_t *bin_info)
{
    ......
    // xf: 计算bitmap基址
    bitmap_t *bitmap = (bitmap_t *)((uintptr_t)run +
        (uintptr_t)bin_info->bitmap_offset);
    ......

    // xf: 获得当前run中第一个free region所在bitmap中的位置
    regind = bitmap_sfu(bitmap, &bin_info->bitmap_info);
    // xf: 计算返回值
    ret = (void *)((uintptr_t)run + (uintptr_t)bin_info->reg0_offset +
        (uintptr_t)(bin_info->reg_interval * regind));
    // xf: free减1
    run->nfree--;
    ......

    return (ret);
}
static inline void *
arena_run_reg_alloc(arena_run_t *run, arena_bin_info_t *bin_info)
{
    ......
    // xf: 计算bitmap基址
    bitmap_t *bitmap = (bitmap_t *)((uintptr_t)run +
        (uintptr_t)bin_info->bitmap_offset);
    ......

    // xf: 获得当前run中第一个free region所在bitmap中的位置
    regind = bitmap_sfu(bitmap, &bin_info->bitmap_info);
    // xf: 计算返回值
    ret = (void *)((uintptr_t)run + (uintptr_t)bin_info->reg0_offset +
        (uintptr_t)(bin_info->reg_interval * regind));
    // xf: free减1
    run->nfree--;
    ......

    return (ret);
}

 

 

其中bitmap_sfu是执行bitmap遍历并安装第一单unset bit. 如2.5节所陈述,
bitmap由
多样组成, 遍历由top level开始循环迭代, 直至bottom level.

其中bitmap_sfu是执行bitmap遍历并设置第一只unset bit. 如2.5节所陈述,
bitmap由
数不胜数组成, 遍历由top level开始循环迭代, 直至bottom level.

JEMALLOC_INLINE size_t
bitmap_sfu(bitmap_t *bitmap, const bitmap_info_t *binfo)
{
    ......
    // xf: 找到最高级level, 并计算ffs
    i = binfo->nlevels - 1;
    g = bitmap[binfo->levels[i].group_offset];
    bit = jemalloc_ffsl(g) - 1;
    // xf: 循环迭代, 直到level0
    while (i > 0) {
        i--;
        // xf: 根据上一级level的结果, 计算当前level的group
        g = bitmap[binfo->levels[i].group_offset + bit];
        // xf: 根据当前level group, 计算下一级需要的bit
        bit = (bit << LG_BITMAP_GROUP_NBITS) + (jemalloc_ffsl(g) - 1);
    }

    // xf: 得到level0的bit, 设置bitmap
    bitmap_set(bitmap, binfo, bit);
    return (bit);
}
JEMALLOC_INLINE size_t
bitmap_sfu(bitmap_t *bitmap, const bitmap_info_t *binfo)
{
    ......
    // xf: 找到最高级level, 并计算ffs
    i = binfo->nlevels - 1;
    g = bitmap[binfo->levels[i].group_offset];
    bit = jemalloc_ffsl(g) - 1;
    // xf: 循环迭代, 直到level0
    while (i > 0) {
        i--;
        // xf: 根据上一级level的结果, 计算当前level的group
        g = bitmap[binfo->levels[i].group_offset + bit];
        // xf: 根据当前level group, 计算下一级需要的bit
        bit = (bit << LG_BITMAP_GROUP_NBITS) + (jemalloc_ffsl(g) - 1);
    }

    // xf: 得到level0的bit, 设置bitmap
    bitmap_set(bitmap, binfo, bit);
    return (bit);
}

 

 

 

 

bitmap_set同普通bitmap操作没有什么区别,
只是当set/unset之后要反向迭代
履新各个高等级level对应之bit位.

bitmap_set同普通bitmap操作没有什么分别,
只是于set/unset之后要反向迭代
履新各个高等级level对应之bit位.

JEMALLOC_INLINE void
bitmap_set(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
{
    ......
    // xf: 计算该bit所在level0中的group
    goff = bit >> LG_BITMAP_GROUP_NBITS;
    // xf: 得到目标group的值g
    gp = &bitmap[goff];
    g = *gp;
    // xf: 根据remainder, 找到target bit, 并反转
    g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
    *gp = g;
    ......
    // xf: 若target bit所在group为0, 则需要更新highlevel的相应bit,
    // 是bitmap_sfu的反向操作.
    if (g == 0) {
        unsigned i;
        for (i = 1; i < binfo->nlevels; i++) {
            bit = goff;
            goff = bit >> LG_BITMAP_GROUP_NBITS;
            gp = &bitmap[binfo->levels[i].group_offset + goff];
            g = *gp;
            assert(g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK)));
            g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
            *gp = g;
            if (g != 0)
                break;
        }
    }
}
JEMALLOC_INLINE void
bitmap_set(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
{
    ......
    // xf: 计算该bit所在level0中的group
    goff = bit >> LG_BITMAP_GROUP_NBITS;
    // xf: 得到目标group的值g
    gp = &bitmap[goff];
    g = *gp;
    // xf: 根据remainder, 找到target bit, 并反转
    g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
    *gp = g;
    ......
    // xf: 若target bit所在group为0, 则需要更新highlevel的相应bit,
    // 是bitmap_sfu的反向操作.
    if (g == 0) {
        unsigned i;
        for (i = 1; i < binfo->nlevels; i++) {
            bit = goff;
            goff = bit >> LG_BITMAP_GROUP_NBITS;
            gp = &bitmap[binfo->levels[i].group_offset + goff];
            g = *gp;
            assert(g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK)));
            g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
            *gp = g;
            if (g != 0)
                break;
        }
    }
}

 

 

 

 

——[ 3.3.2 – arena_bin_malloc_hard

——[ 3.3.2 – arena_bin_malloc_hard

  1. 自打bin中获取可用之nonfull run, 这个历程中bin->lock有或被解锁.
  2. 临时勿以new run, 返回检查bin->runcur是否重可用. 如果是,
       则一直当里边分配region(其他线程在bin lock解锁期间可能提早
       修改了runcur). 否则, 执行4.
  3. 再检讨1蒙受获得的new run, 如果为空, 则放该run.
       否则跟当下runcur作比较, 若地址低于runcur, 则与那开交换.
       将旧的runcur插回run tree. 并返回new rigion.
  4. 故而new run填充runcur, 并在其间分配region, 返回.
  1. 起bin中获可用之nonfull run, 这个历程中bin->lock有或让解锁.
  2. 小未使用new run, 返回检查bin->runcur是否更可用. 如果是,
       则一直当中间分配region(其他线程在bin lock解锁期间可能提前
       修改了runcur). 否则, 执行4.
  3. 更检查1饱受得的new run, 如果为空, 则放该run.
       否则与时runcur作比较, 若地址低于runcur, 则与那做交换.
       将旧的runcur插回run tree. 并返回new rigion.
  4. 故new run填充runcur, 并在中间分配region, 返回.
static void *
arena_bin_malloc_hard(arena_t *arena, arena_bin_t *bin)
{
    ......
    // xf: 获得bin对应的arena_bin_info, 并将current run置空
    binind = arena_bin_index(arena, bin);
    bin_info = &arena_bin_info[binind];
    bin->runcur = NULL;

    // xf: 从指定bin中获得一个可用的run
    run = arena_bin_nonfull_run_get(arena, bin);

    // 对bin->runcur做重新检查. 如果可用且未耗尽, 则直接分配.
    if (bin->runcur != NULL && bin->runcur->nfree > 0) {
        ret = arena_run_reg_alloc(bin->runcur, bin_info);

        // xf: 若new run为空, 则将其释放. 否则重新插入run tree.
        if (run != NULL) {
            arena_chunk_t *chunk;
            chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
            if (run->nfree == bin_info->nregs)
                arena_dalloc_bin_run(arena, chunk, run, bin);
            else
                arena_bin_lower_run(arena, chunk, run, bin);
        }
        return (ret);
    }

    if (run == NULL)
        return (NULL);

    // xf: 到这里在bin->runcur中分配失败, 用当前新获得的run填充current run
    bin->runcur = run;

    // xf: 在new run中分配region
    return (arena_run_reg_alloc(bin->runcur, bin_info));
}
static void *
arena_bin_malloc_hard(arena_t *arena, arena_bin_t *bin)
{
    ......
    // xf: 获得bin对应的arena_bin_info, 并将current run置空
    binind = arena_bin_index(arena, bin);
    bin_info = &arena_bin_info[binind];
    bin->runcur = NULL;

    // xf: 从指定bin中获得一个可用的run
    run = arena_bin_nonfull_run_get(arena, bin);

    // 对bin->runcur做重新检查. 如果可用且未耗尽, 则直接分配.
    if (bin->runcur != NULL && bin->runcur->nfree > 0) {
        ret = arena_run_reg_alloc(bin->runcur, bin_info);

        // xf: 若new run为空, 则将其释放. 否则重新插入run tree.
        if (run != NULL) {
            arena_chunk_t *chunk;
            chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
            if (run->nfree == bin_info->nregs)
                arena_dalloc_bin_run(arena, chunk, run, bin);
            else
                arena_bin_lower_run(arena, chunk, run, bin);
        }
        return (ret);
    }

    if (run == NULL)
        return (NULL);

    // xf: 到这里在bin->runcur中分配失败, 用当前新获得的run填充current run
    bin->runcur = run;

    // xf: 在new run中分配region
    return (arena_run_reg_alloc(bin->runcur, bin_info));
}

 

 

——[ 3.3.3 – arena_bin_nonfull_run_get

——[ 3.3.3 – arena_bin_nonfull_run_get

  1. 尝以当下run tree中寻找可用run, 成功则回, 否则进入下一致步
  2. 解锁bin lock, 并加锁arena lock, 尝试在手上arena中分配new run.
       之后再解锁arena lock, 并加锁bin lock. 如果成功则回, 否则
       进入下一致步.
  3. 分配失败, 重新当脚下run tree中找找相同方方面面可用run.
  1. 品味在时run tree中寻找可用run, 成功则回, 否则进入下一致步
  2. 解锁bin lock, 并加锁arena lock, 尝试在现阶段arena中分红new run.
       之后更解锁arena lock, 并加锁bin lock. 如果成功则归, 否则
       进入下一样步.
  3. 分配失败, 重新在脚下run tree中觅相同通可用run.
static arena_run_t *
arena_bin_nonfull_run_get(arena_t *arena, arena_bin_t *bin)
{
    ......
    // xf: 尝试从当前run tree中寻找一个可用run, 如果存在就返回
    run = arena_bin_nonfull_run_tryget(bin);
    if (run != NULL)
        return (run);    
    ......

    // xf: 打开bin lock, 让其他线程可以操作当前的bin tree
    malloc_mutex_unlock(&bin->lock);
    // xf: 锁住arena lock, 以分配new run
    malloc_mutex_lock(&arena->lock);

    // xf: 尝试分配new run
    run = arena_run_alloc_small(arena, bin_info->run_size, binind);
    if (run != NULL) {
        // 初始化new run和bitmap
        bitmap_t *bitmap = (bitmap_t *)((uintptr_t)run +
            (uintptr_t)bin_info->bitmap_offset);

        run->bin = bin;
        run->nextind = 0;
        run->nfree = bin_info->nregs;
        bitmap_init(bitmap, &bin_info->bitmap_info);
    }

    // xf: 解锁arena lock
    malloc_mutex_unlock(&arena->lock);
    // xf: 重新加锁bin lock
    malloc_mutex_lock(&bin->lock);

    if (run != NULL) {
        ......
        return (run);
    }

    // xf: 如果run alloc失败, 则回过头重新try get一次(前面解锁bin lock
    // 给了其他线程机会).
    run = arena_bin_nonfull_run_tryget(bin);
    if (run != NULL)
        return (run);

    return (NULL);
}
static arena_run_t *
arena_bin_nonfull_run_get(arena_t *arena, arena_bin_t *bin)
{
    ......
    // xf: 尝试从当前run tree中寻找一个可用run, 如果存在就返回
    run = arena_bin_nonfull_run_tryget(bin);
    if (run != NULL)
        return (run);    
    ......

    // xf: 打开bin lock, 让其他线程可以操作当前的bin tree
    malloc_mutex_unlock(&bin->lock);
    // xf: 锁住arena lock, 以分配new run
    malloc_mutex_lock(&arena->lock);

    // xf: 尝试分配new run
    run = arena_run_alloc_small(arena, bin_info->run_size, binind);
    if (run != NULL) {
        // 初始化new run和bitmap
        bitmap_t *bitmap = (bitmap_t *)((uintptr_t)run +
            (uintptr_t)bin_info->bitmap_offset);

        run->bin = bin;
        run->nextind = 0;
        run->nfree = bin_info->nregs;
        bitmap_init(bitmap, &bin_info->bitmap_info);
    }

    // xf: 解锁arena lock
    malloc_mutex_unlock(&arena->lock);
    // xf: 重新加锁bin lock
    malloc_mutex_lock(&bin->lock);

    if (run != NULL) {
        ......
        return (run);
    }

    // xf: 如果run alloc失败, 则回过头重新try get一次(前面解锁bin lock
    // 给了其他线程机会).
    run = arena_bin_nonfull_run_tryget(bin);
    if (run != NULL)
        return (run);

    return (NULL);
}

 

 

——[ 3.3.4 – Small Run Alloc

——[ 3.3.4 – Small Run Alloc

  1. 自打arena avail tree上得一个可用run, 并对该切割. 失败进入下同样步.
  2. 品让arena分配新的chunk, 以结构new run. 此过程也许会见解锁arena
    lock.
       失败进入下一样步.
  3. 外线程可能于斯过程被释放了少数run, 重新检查avail-tree,
    尝试获取run.
  1. 从今arena avail tree上赢得一个可用run, 并对那切割. 失败进入下一样步.
  2. 品给arena分配新的chunk, 以结构new run. 此过程也许会见解锁arena
    lock.
       失败进入下一样步.
  3. 另线程可能以斯过程中自由了某些run, 重新检查avail-tree,
    尝试获取run.
static arena_run_t *
arena_run_alloc_small(arena_t *arena, size_t size, size_t binind)
{
    ......
    // xf: 从available tree上尝试寻找并切割一个合适的run, 并对其初始化
    run = arena_run_alloc_small_helper(arena, size, binind);
    if (run != NULL)
        return (run);

    // xf: 当前arena内没有可用的空闲run, 构造一个新的chunk以分配new run.
    chunk = arena_chunk_alloc(arena);
    if (chunk != NULL) {
        run = (arena_run_t *)((uintptr_t)chunk + (map_bias << LG_PAGE));
        arena_run_split_small(arena, run, size, binind);
        return (run);
    }

    // xf: 重新检查arena avail-tree.
    return (arena_run_alloc_small_helper(arena, size, binind));
}

static arena_run_t *
arena_run_alloc_small_helper(arena_t *arena, size_t size, size_t binind)
{
    ......
    // xf: 在arena的available tree中寻找一个大于等于size大小的最小run
    key = (arena_chunk_map_t *)(size | CHUNK_MAP_KEY);
    mapelm = arena_avail_tree_nsearch(&arena->runs_avail, key);
    if (mapelm != NULL) {
        arena_chunk_t *run_chunk = CHUNK_ADDR2BASE(mapelm);
        size_t pageind = arena_mapelm_to_pageind(mapelm);

        // xf: 计算候选run的地址
        run = (arena_run_t *)((uintptr_t)run_chunk + (pageind <<
            LG_PAGE));
        // xf: 根据分配需求, 切割候选run
        arena_run_split_small(arena, run, size, binind);
        return (run);
    }

    return (NULL);
}
static arena_run_t *
arena_run_alloc_small(arena_t *arena, size_t size, size_t binind)
{
    ......
    // xf: 从available tree上尝试寻找并切割一个合适的run, 并对其初始化
    run = arena_run_alloc_small_helper(arena, size, binind);
    if (run != NULL)
        return (run);

    // xf: 当前arena内没有可用的空闲run, 构造一个新的chunk以分配new run.
    chunk = arena_chunk_alloc(arena);
    if (chunk != NULL) {
        run = (arena_run_t *)((uintptr_t)chunk + (map_bias << LG_PAGE));
        arena_run_split_small(arena, run, size, binind);
        return (run);
    }

    // xf: 重新检查arena avail-tree.
    return (arena_run_alloc_small_helper(arena, size, binind));
}

static arena_run_t *
arena_run_alloc_small_helper(arena_t *arena, size_t size, size_t binind)
{
    ......
    // xf: 在arena的available tree中寻找一个大于等于size大小的最小run
    key = (arena_chunk_map_t *)(size | CHUNK_MAP_KEY);
    mapelm = arena_avail_tree_nsearch(&arena->runs_avail, key);
    if (mapelm != NULL) {
        arena_chunk_t *run_chunk = CHUNK_ADDR2BASE(mapelm);
        size_t pageind = arena_mapelm_to_pageind(mapelm);

        // xf: 计算候选run的地址
        run = (arena_run_t *)((uintptr_t)run_chunk + (pageind <<
            LG_PAGE));
        // xf: 根据分配需求, 切割候选run
        arena_run_split_small(arena, run, size, binind);
        return (run);
    }

    return (NULL);
}

 

 

切割small run主要分为4步,

切割small run主要分为4步,

  1. 将候选run的arena_chunk_map_t节点从avail-tree上摘除.
  2. 基于节点储存的原始page信息, 以及need pages信息, 切割该run.
  3. 创新remainder节点信息(只待创新首尾page), 重新插avail-tree.
  4. 设置切割后new run所有page对应之map节点信息(根据2.3.3节所述).
  1. 将候选run的arena_chunk_map_t节点从avail-tree上摘除.
  2. 因节点储存的原始page信息, 以及need pages信息, 切割该run.
  3. 履新remainder节点信息(只需要创新首尾page), 重新插avail-tree.
  4. 安装切割后new run所有page对应的map节点信息(根据2.3.3节所述).
static void
arena_run_split_small(arena_t *arena, arena_run_t *run, size_t size,
    size_t binind)
{
    ......
    // xf: 获取目标run的dirty flag
    chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
    run_ind = (unsigned)(((uintptr_t)run - (uintptr_t)chunk) >> LG_PAGE);
    flag_dirty = arena_mapbits_dirty_get(chunk, run_ind);
    need_pages = (size >> LG_PAGE);

    // xf: 1. 将候选run从available tree上摘除
    //     2. 根据need pages对候选run进行切割
    //     3. 将remainder重新插入available tree    
    arena_run_split_remove(arena, chunk, run_ind, flag_dirty, need_pages);

    // xf: 设置刚刚被split后的run的第一个page
    arena_mapbits_small_set(chunk, run_ind, 0, binind, flag_dirty);
    ......

    // xf: 依次设置run中的其他page, run index依次递增
    for (i = 1; i < need_pages - 1; i++) {
        arena_mapbits_small_set(chunk, run_ind+i, i, binind, 0);
        .......
    }

    // xf: 设置run中的最后一个page
    arena_mapbits_small_set(chunk, run_ind+need_pages-1, need_pages-1,
        binind, flag_dirty);
    ......
}
static void
arena_run_split_small(arena_t *arena, arena_run_t *run, size_t size,
    size_t binind)
{
    ......
    // xf: 获取目标run的dirty flag
    chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
    run_ind = (unsigned)(((uintptr_t)run - (uintptr_t)chunk) >> LG_PAGE);
    flag_dirty = arena_mapbits_dirty_get(chunk, run_ind);
    need_pages = (size >> LG_PAGE);

    // xf: 1. 将候选run从available tree上摘除
    //     2. 根据need pages对候选run进行切割
    //     3. 将remainder重新插入available tree    
    arena_run_split_remove(arena, chunk, run_ind, flag_dirty, need_pages);

    // xf: 设置刚刚被split后的run的第一个page
    arena_mapbits_small_set(chunk, run_ind, 0, binind, flag_dirty);
    ......

    // xf: 依次设置run中的其他page, run index依次递增
    for (i = 1; i < need_pages - 1; i++) {
        arena_mapbits_small_set(chunk, run_ind+i, i, binind, 0);
        .......
    }

    // xf: 设置run中的最后一个page
    arena_mapbits_small_set(chunk, run_ind+need_pages-1, need_pages-1,
        binind, flag_dirty);
    ......
}

 

 

 

 

——[ 3.3.5 – Chunk Alloc

——[ 3.3.5 – Chunk Alloc

arena获取chunk一般生三三两两个途径. 其一是透过中的spare指针.
该指针缓存了近期
一致坏chunk被假释的记录. 因此该法速度很快. 另一样种植更加健康,
通过内片配函
勤分配, 最终将由chunk_alloc_core执行. 但于Je的计划性受到, 执行arena
chunk的分开
配器是可定制的, 你得轮换任何第三着chunk分配器. 这里只有讨论默认情况.

arena获取chunk一般生点儿只途径. 其一是经内的spare指针.
该指针缓存了近来
平等蹩脚chunk被假释的记录. 因此该办法速度很快. 另一样种植更加健康,
通过外片配函
往往分配, 最终将由chunk_alloc_core执行. 但在Je的计划中, 执行arena
chunk的分开
配器是可定制的, 你可以轮换任何第三正chunk分配器. 这里就讨论默认情况.

Je在chunk_alloc_core中以及传统分配器如Dl有比充分区别. 通常情况下,
从网获得
内存无非是morecore或mmap两种方式. Dl中遵循预morecore->mmap的依次,
而Je更
否活, 具体的顺序由dss_prec_t决定.

Je在chunk_alloc_core中以及传统分配器如Dl有比充分区别. 通常情况下,
从网获得
内存无非是morecore或mmap两种植方式. Dl中本优先morecore->mmap的依次,
而Je更
呢活, 具体的顺序由dss_prec_t决定.

欠项目是一个枚举, 定义如下,

拖欠品种是一个枚举, 定义如下,

typedef enum {
    dss_prec_disabled  = 0,
    dss_prec_primary   = 1,
    dss_prec_secondary = 2,
    dss_prec_limit     = 3
} dss_prec_t;
typedef enum {
    dss_prec_disabled  = 0,
    dss_prec_primary   = 1,
    dss_prec_secondary = 2,
    dss_prec_limit     = 3
} dss_prec_t;

 

 

 

 

此处dss和morecore含义是千篇一律之. primary表示先dss,
secondary则先mmap.
Je默认使用后者.

这边dss和morecore含义是相同的. primary表示先dss,
secondary则先mmap.
Je默认使用后者.

实质上分配时, 无论以哪种政策, 都见面优先实施chunk_recycle, 再执行chunk
alloc, 如下,

其实分配时, 无论使用哪种政策, 都见面先实施chunk_recycle, 再执行chunk
alloc, 如下,

static void *
chunk_alloc_core(size_t size, size_t alignment, bool base, bool *zero,
    dss_prec_t dss_prec)
{
    void *ret;

    if (have_dss && dss_prec == dss_prec_primary) {
        if ((ret = chunk_recycle(&chunks_szad_dss, &chunks_ad_dss, size,
            alignment, base, zero)) != NULL)
            return (ret);
        if ((ret = chunk_alloc_dss(size, alignment, zero)) != NULL)
            return (ret);
    }

    if ((ret = chunk_recycle(&chunks_szad_mmap, &chunks_ad_mmap, size,
        alignment, base, zero)) != NULL)
        return (ret);
    if ((ret = chunk_alloc_mmap(size, alignment, zero)) != NULL)
        return (ret);

    if (have_dss && dss_prec == dss_prec_secondary) {
        if ((ret = chunk_recycle(&chunks_szad_dss, &chunks_ad_dss, size,
            alignment, base, zero)) != NULL)
            return (ret);
        if ((ret = chunk_alloc_dss(size, alignment, zero)) != NULL)
            return (ret);
    }

    return (NULL);
}
static void *
chunk_alloc_core(size_t size, size_t alignment, bool base, bool *zero,
    dss_prec_t dss_prec)
{
    void *ret;

    if (have_dss && dss_prec == dss_prec_primary) {
        if ((ret = chunk_recycle(&chunks_szad_dss, &chunks_ad_dss, size,
            alignment, base, zero)) != NULL)
            return (ret);
        if ((ret = chunk_alloc_dss(size, alignment, zero)) != NULL)
            return (ret);
    }

    if ((ret = chunk_recycle(&chunks_szad_mmap, &chunks_ad_mmap, size,
        alignment, base, zero)) != NULL)
        return (ret);
    if ((ret = chunk_alloc_mmap(size, alignment, zero)) != NULL)
        return (ret);

    if (have_dss && dss_prec == dss_prec_secondary) {
        if ((ret = chunk_recycle(&chunks_szad_dss, &chunks_ad_dss, size,
            alignment, base, zero)) != NULL)
            return (ret);
        if ((ret = chunk_alloc_dss(size, alignment, zero)) != NULL)
            return (ret);
    }

    return (NULL);
}

 

 

 

 

所谓chunk recycle是以alloc chunk之前, 优先在废弃的chunk
tree上摸索可用chunk,
连分配base node以储存meta data的过程. 好处是彼同样可以加速分配速度,
其二凡要是
空间分配更严密, 并节省内存.

所谓chunk recycle是以alloc chunk之前, 优先在废除之chunk
tree上寻找可用chunk,
连分配base node以储存meta data的过程. 好处是那同可以加快分配速度,
其二凡要是
空间分配更加严谨, 并节省内存.

每当Je中留存4棵全局的rb tree, 分别吗,

于Je中存在4棵全局的rb tree, 分别吗,

static extent_tree_t    chunks_szad_mmap;
static extent_tree_t    chunks_ad_mmap;
static extent_tree_t    chunks_szad_dss;
static extent_tree_t    chunks_ad_dss;
static extent_tree_t    chunks_szad_mmap;
static extent_tree_t    chunks_ad_mmap;
static extent_tree_t    chunks_szad_dss;
static extent_tree_t    chunks_ad_dss;

 

 

它分别对应mmap和dss方式. 当一个chunk或huge region被放后, 将采访至
随即4株tree中. szad和ad在内容达到连无本质区别, 只是寻觅方式不一样.
前者采用
先size后address的章程, 后者则是纯address的检索.

她各自对应mmap和dss方式. 当一个chunk或huge region被放走后, 将集至
就4株tree中. szad和ad在情节达并无本质区别, 只是摸索方式不一样.
前者采用
先size后address的方式, 后者则是纯address的检索.

recycle算法概括如下,

recycle算法概括如下,

  1. 自我批评base标志, 如果为真则直接回到, 否则进下一样步.
       开始之反省是必要的, 因为recycle过程中或者会见创新的extent node,
    要求
       调用base allocator分配. 另一方面, base
    alloc可能坐耗尽的原因要反过
       来调用chunk alloc. 如此将招致dead loop.
  2. 根据alignment计算分配大小, 并在szad
    tree(mmap还是dss需要上一级决定)上
       寻找一个盖等于alloc size的极度小node.
  3. chunk tree上之node未必对齐到alignment上, 将地址指向同,
    之后用沾leadsize
       和trailsize.
  4. 以原本node从chunk tree上remove. 若leadsize不为0,
    则将那当作新的chunk重新
       insert回chunk tree. trailsize不为0的情亦然.
    若leadsize和trailsize同时
       不为0, 则通过base_node_alloc为trailsize生成新的node并插入. 若base
    alloc
       失败, 则整个新分配的region都设销售毁.
  5. 倘leadsize和trailsize都为0, 则将node(注意就是节点)释放.
    返回对齐后底
       chunk地址.
  1. 检查base标志, 如果为真则一直回到, 否则进下一致步.
       开始之反省是不可或缺的, 因为recycle过程遭到恐会见创新的extent node,
    要求
       调用base allocator分配. 另一方面, base
    alloc可能为耗尽的原因一旦反过
       来调用chunk alloc. 如此将造成dead loop.
  2. 冲alignment计算分配大小, 并在szad
    tree(mmap还是dss需要上一级决定)上
       寻找一个盖等于alloc size的尽小node.
  3. chunk tree上之node未必对齐到alignment上, 将地址指向旅,
    之后用取leadsize
       和trailsize.
  4. 将本node从chunk tree上remove. 若leadsize不为0,
    则将那个用作新的chunk重新
       insert回chunk tree. trailsize不为0的图景亦然.
    若leadsize和trailsize同时
       不为0, 则通过base_node_alloc为trailsize生成新的node并插入. 若base
    alloc
       失败, 则整个新分配的region都设销售毁.
  5. 万一leadsize和trailsize都也0, 则将node(注意就是节点)释放.
    返回对齐后的
       chunk地址.
static void *
chunk_recycle(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, size_t size,
    size_t alignment, bool base, bool *zero)
{
    ......
    // xf: 由于构造extent_node时可能因为内存不足的原因, 同样需要构造chunk,
    // 这样就导致recursively dead loop. 因此依靠base标志, 区分普通alloc和
    // base node alloc. 如果是base alloc, 则立即返回.
    if (base) {
        return (NULL);
    }

    // xf: 计算分配大小
    alloc_size = size + alignment - chunksize;
    ......
    key.addr = NULL;
    key.size = alloc_size;

    // xf: 在指定的szad tree上寻找大于等于alloc size的最小可用node
    malloc_mutex_lock(&chunks_mtx);
    node = extent_tree_szad_nsearch(chunks_szad, &key);
    ......

    // xf: 将候选节点基址对齐到分配边界上, 并计算leadsize, trailsize
    // 以及返回地址.
    leadsize = ALIGNMENT_CEILING((uintptr_t)node->addr, alignment) -
        (uintptr_t)node->addr;
    trailsize = node->size - leadsize - size;
    ret = (void *)((uintptr_t)node->addr + leadsize);
    ......

    // xf: 将原node从szad/ad tree上移除
    extent_tree_szad_remove(chunks_szad, node);
    extent_tree_ad_remove(chunks_ad, node);

    // xf: 如果存在leadsize, 则将前面多余部分作为一个chunk重新插入
    // szad/ad tree上.
    if (leadsize != 0) {
        node->size = leadsize;
        extent_tree_szad_insert(chunks_szad, node);
        extent_tree_ad_insert(chunks_ad, node);
        node = NULL;
    }

    // xf: 同样如果存在trailsize, 也将后面的多余部分插入.
    if (trailsize != 0) {
        // xf: 如果leadsize不为0, 这时原来的extent_node已经被用过了,
        // 则必须为trailsize部分重新分配新的extent_node
        if (node == NULL) {
            malloc_mutex_unlock(&chunks_mtx);
            node = base_node_alloc();
            ......
        }
        // xf: 计算trail chunk, 并插入
        node->addr = (void *)((uintptr_t)(ret) + size);
        node->size = trailsize;
        node->zeroed = zeroed;
        extent_tree_szad_insert(chunks_szad, node);
        extent_tree_ad_insert(chunks_ad, node);
        node = NULL;
    }
    malloc_mutex_unlock(&chunks_mtx);

    // xf: leadsize & basesize都不存在, 将node释放.
    if (node != NULL)
        base_node_dalloc(node);
    ......

    return (ret);
}
static void *
chunk_recycle(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, size_t size,
    size_t alignment, bool base, bool *zero)
{
    ......
    // xf: 由于构造extent_node时可能因为内存不足的原因, 同样需要构造chunk,
    // 这样就导致recursively dead loop. 因此依靠base标志, 区分普通alloc和
    // base node alloc. 如果是base alloc, 则立即返回.
    if (base) {
        return (NULL);
    }

    // xf: 计算分配大小
    alloc_size = size + alignment - chunksize;
    ......
    key.addr = NULL;
    key.size = alloc_size;

    // xf: 在指定的szad tree上寻找大于等于alloc size的最小可用node
    malloc_mutex_lock(&chunks_mtx);
    node = extent_tree_szad_nsearch(chunks_szad, &key);
    ......

    // xf: 将候选节点基址对齐到分配边界上, 并计算leadsize, trailsize
    // 以及返回地址.
    leadsize = ALIGNMENT_CEILING((uintptr_t)node->addr, alignment) -
        (uintptr_t)node->addr;
    trailsize = node->size - leadsize - size;
    ret = (void *)((uintptr_t)node->addr + leadsize);
    ......

    // xf: 将原node从szad/ad tree上移除
    extent_tree_szad_remove(chunks_szad, node);
    extent_tree_ad_remove(chunks_ad, node);

    // xf: 如果存在leadsize, 则将前面多余部分作为一个chunk重新插入
    // szad/ad tree上.
    if (leadsize != 0) {
        node->size = leadsize;
        extent_tree_szad_insert(chunks_szad, node);
        extent_tree_ad_insert(chunks_ad, node);
        node = NULL;
    }

    // xf: 同样如果存在trailsize, 也将后面的多余部分插入.
    if (trailsize != 0) {
        // xf: 如果leadsize不为0, 这时原来的extent_node已经被用过了,
        // 则必须为trailsize部分重新分配新的extent_node
        if (node == NULL) {
            malloc_mutex_unlock(&chunks_mtx);
            node = base_node_alloc();
            ......
        }
        // xf: 计算trail chunk, 并插入
        node->addr = (void *)((uintptr_t)(ret) + size);
        node->size = trailsize;
        node->zeroed = zeroed;
        extent_tree_szad_insert(chunks_szad, node);
        extent_tree_ad_insert(chunks_ad, node);
        node = NULL;
    }
    malloc_mutex_unlock(&chunks_mtx);

    // xf: leadsize & basesize都不存在, 将node释放.
    if (node != NULL)
        base_node_dalloc(node);
    ......

    return (ret);
}

 

 

 

 

好端端分配方式先期来拘禁dss. 由于dss是和目前过程的brk指针相关的,
任何线程(包括可能
莫通过Je执行分配的线程)都出且修改该指针值. 因此,
首先要管dss指针调整及对共同在
chunksize边界的职务, 否则过多跟chunk相关的精打细算都见面去效. 接下,
还要做第二糟糕
调动对旅到以外请求的alignment边界. 在是基础及再也拓展分配.

好端端分配办法先期来拘禁dss. 由于dss是与当下经过的brk指针相关的,
任何线程(包括可能
勿通过Je执行分配的线程)都出且修改该指针值. 因此,
首先要把dss指针调整至对一起在
chunksize边界的职务, 否则过多跟chunk相关的算计都见面错过效. 接下去,
还要举行第二次等
调对一头到外请求的alignment边界. 在此基础及再次展开分配.

及dss分配相关的变量如下,

以及dss分配相关的变量如下,

static malloc_mutex_t    dss_mtx;
static void        *dss_base;      
static void        *dss_prev;      
static void        *dss_max;       
static malloc_mutex_t    dss_mtx;
static void        *dss_base;      
static void        *dss_prev;      
static void        *dss_max;       

 

 

 

 

dss_mtx:  dss lock. 注意其并无克自及维护dss指针的图,
因为brk是一个系统资源.
          该lock保护之凡dss_prev, dss_max指针.
dss_base: 只在chunk_dss_boot时更新一不行.
主要为此作识别chunk在线性地址空间受到所
          处的职务, 与mmap作出区别.
dss_prev: 当前dss指针, 是系统brk指针的副本, 值等于-1象征dss耗尽.
dss_max:  当前dss区域及限.

dss_mtx:  dss lock. 注意该并无克由至保护dss指针的用意,
因为brk是一个系统资源.
          该lock保护的凡dss_prev, dss_max指针.
dss_base: 只在chunk_dss_boot时更新一破.
主要用作识别chunk在线性地址空间被所
          处的职, 与mmap作出区别.
dss_prev: 当前dss指针, 是系brk指针的副本, 值等于-1意味dss耗尽.
dss_max:  当前dss区域达到限.

dss alloc算法如下,

dss alloc算法如下,

  1. 获取brk指针, 更新到dss_max.
  2. 将dss_max对齐到chunksize边界上, 计算padding大小gap_size
  3. 再将dss_max对齐到aligment边界上, 得到cpad_size
  4. 计需要分配的尺寸, 并尝试sbrk
         incr = gap_size + cpad_size + size
  5. 分红成功, 检查cpad是否非0, 是虽然以立即部分复转收.
    而gap_size部分因
       不可用则给废弃.
  6. 如分配失败, 则检查dss是否耗尽, 如果没有则赶回1双重尝试. 否则返回.
  1. 获取brk指针, 更新到dss_max.
  2. 将dss_max对齐到chunksize边界上, 计算padding大小gap_size
  3. 再将dss_max对齐到aligment边界上, 得到cpad_size
  4. 计算需要分配的轻重, 并尝试sbrk
         incr = gap_size + cpad_size + size
  5. 分配成功, 检查cpad是否非0, 是虽然拿这一部分复转收.
    而gap_size部分因为
       不可用则叫废弃.
  6. 如果分配失败, 则检查dss是否耗尽, 如果没有则回1双重尝试. 否则返回.

示意图,

示意图,

chunk_base             cpad        ret        dss_next
    |                   |           |            |
    v                   v           v            v
    +--------+----------+-----------+------   ---+
    |  used  | gap_size | cpad_size | size ...   |
    +--------+----------+-----------+------   ---+
             |<------------- incr -------------->|            
             ^          ^           ^  
             |          |           |
          dss_max  chunk_base +     +-- chunk_base +
                     chunk_size          alignment
chunk_base             cpad        ret        dss_next
    |                   |           |            |
    v                   v           v            v
    +--------+----------+-----------+------   ---+
    |  used  | gap_size | cpad_size | size ...   |
    +--------+----------+-----------+------   ---+
             |<------------- incr -------------->|            
             ^          ^           ^  
             |          |           |
          dss_max  chunk_base +     +-- chunk_base +
                     chunk_size          alignment

 

 

 

 

源码注释,

源码注释,

void *
chunk_alloc_dss(size_t size, size_t alignment, bool *zero)
{
    ......    
    // xf: dss是否耗尽
    malloc_mutex_lock(&dss_mtx);
    if (dss_prev != (void *)-1) {
        ......
        do {
            // xf: 获取当前dss指针
            dss_max = chunk_dss_sbrk(0);

            // xf: 计算对齐到chunk size边界需要的padding大小
            gap_size = (chunksize - CHUNK_ADDR2OFFSET(dss_max)) &
                chunksize_mask;
            // xf: 对齐到chunk边界的chunk起始地址
            cpad = (void *)((uintptr_t)dss_max + gap_size);
            // xf: 对齐到alignment边界的起始地址
            ret = (void *)ALIGNMENT_CEILING((uintptr_t)dss_max,
                alignment);
            cpad_size = (uintptr_t)ret - (uintptr_t)cpad;
            // xf: dss_max分配后的更新地址
            dss_next = (void *)((uintptr_t)ret + size);
            ......
            incr = gap_size + cpad_size + size;
            // xf: 从dss分配
            dss_prev = chunk_dss_sbrk(incr);
            if (dss_prev == dss_max) {

                dss_max = dss_next;
                malloc_mutex_unlock(&dss_mtx);
                // xf: 如果ret和cpad对齐不在同一个位置, 则将cpad开始
                // cpad_size大小的内存回收到szad/ad tree中. 而以之前
                // dss起始的gap_size大小内存由于本身并非对齐到
                // chunk_size, 则被废弃.
                if (cpad_size != 0)
                    chunk_unmap(cpad, cpad_size);
                ......
                return (ret);
            }
        } while (dss_prev != (void *)-1);   // xf: 反复尝试直至dss耗尽
    }
    malloc_mutex_unlock(&dss_mtx);

    return (NULL);
}
void *
chunk_alloc_dss(size_t size, size_t alignment, bool *zero)
{
    ......    
    // xf: dss是否耗尽
    malloc_mutex_lock(&dss_mtx);
    if (dss_prev != (void *)-1) {
        ......
        do {
            // xf: 获取当前dss指针
            dss_max = chunk_dss_sbrk(0);

            // xf: 计算对齐到chunk size边界需要的padding大小
            gap_size = (chunksize - CHUNK_ADDR2OFFSET(dss_max)) &
                chunksize_mask;
            // xf: 对齐到chunk边界的chunk起始地址
            cpad = (void *)((uintptr_t)dss_max + gap_size);
            // xf: 对齐到alignment边界的起始地址
            ret = (void *)ALIGNMENT_CEILING((uintptr_t)dss_max,
                alignment);
            cpad_size = (uintptr_t)ret - (uintptr_t)cpad;
            // xf: dss_max分配后的更新地址
            dss_next = (void *)((uintptr_t)ret + size);
            ......
            incr = gap_size + cpad_size + size;
            // xf: 从dss分配
            dss_prev = chunk_dss_sbrk(incr);
            if (dss_prev == dss_max) {

                dss_max = dss_next;
                malloc_mutex_unlock(&dss_mtx);
                // xf: 如果ret和cpad对齐不在同一个位置, 则将cpad开始
                // cpad_size大小的内存回收到szad/ad tree中. 而以之前
                // dss起始的gap_size大小内存由于本身并非对齐到
                // chunk_size, 则被废弃.
                if (cpad_size != 0)
                    chunk_unmap(cpad, cpad_size);
                ......
                return (ret);
            }
        } while (dss_prev != (void *)-1);   // xf: 反复尝试直至dss耗尽
    }
    malloc_mutex_unlock(&dss_mtx);

    return (NULL);
}

 

 

 

 

说到底介绍chunk_alloc_mmap. 同dss方式类似, mmap也有对齐的问题.
由于系统mmap
调用无法指定alignment, 因此Je实现了一个得兑现对齐但速度还慢的mmap
slow方式.
当弥补, 在chunk alloc mmap时先尝试按照普通方式mmap,
如果返回值恰好满足对伙同
渴求则直归(多数状态下是有效的). 否则将赶回值munmap, 再调用mmap
slow.

最终介绍chunk_alloc_mmap. 同dss方式类似, mmap也存对齐的题目.
由于系统mmap
调用无法指定alignment, 因此Je实现了一个得以兑现对齐但速度更慢的mmap
slow方式.
作为弥补, 在chunk alloc mmap时先尝试以普通方式mmap,
如果返回值恰好满足对共同
求则一直返回(多数场面下是立竿见影之). 否则将回值munmap, 再调用mmap
slow.

void *
chunk_alloc_mmap(size_t size, size_t alignment, bool *zero)
{
    ......
    ret = pages_map(NULL, size);
    if (ret == NULL)
        return (NULL);
    offset = ALIGNMENT_ADDR2OFFSET(ret, alignment);
    if (offset != 0) {
        pages_unmap(ret, size);
        return (chunk_alloc_mmap_slow(size, alignment, zero));
    }
    ......

    return (ret);
}
void *
chunk_alloc_mmap(size_t size, size_t alignment, bool *zero)
{
    ......
    ret = pages_map(NULL, size);
    if (ret == NULL)
        return (NULL);
    offset = ALIGNMENT_ADDR2OFFSET(ret, alignment);
    if (offset != 0) {
        pages_unmap(ret, size);
        return (chunk_alloc_mmap_slow(size, alignment, zero));
    }
    ......

    return (ret);
}

 

 

 

 

mmap slow通过事先分配超量size, 对齐后再实行trim,
去丢前后余量实现mmap对齐.
page trim通过简单不行munmap将leadsize和trailsize部分各自释放. 因此理论及,
mmap
slow需要极度多三蹩脚munmap.

mmap slow通过事先分配超量size, 对齐后再也履行trim,
去丢前后余量实现mmap对齐.
page trim通过简单差munmap将leadsize和trailsize部分各自释放. 因此理论及,
mmap
slow需要极多三浅munmap.

    |<-------------alloc_size---------->|
    +-----------+-----   --+------------+
    | lead_size | size...  | trail_size |
    +-----------+-----   --+------------+
    ^           ^
    |           |
    pages      ret(alignment)

static void *
chunk_alloc_mmap_slow(size_t size, size_t alignment, bool *zero)
{
    ......
    alloc_size = size + alignment - PAGE;
    if (alloc_size < size)
        return (NULL);
    do {
        pages = pages_map(NULL, alloc_size);
        if (pages == NULL)
            return (NULL);
        leadsize = ALIGNMENT_CEILING((uintptr_t)pages, alignment) -
            (uintptr_t)pages;
        ret = pages_trim(pages, alloc_size, leadsize, size);
    } while (ret == NULL);
    ......
    return (ret);
}

static void *
pages_trim(void *addr, size_t alloc_size, size_t leadsize, size_t size)
{
    void *ret = (void *)((uintptr_t)addr + leadsize);
    ......
    {
        size_t trailsize = alloc_size - leadsize - size;

        if (leadsize != 0)
            pages_unmap(addr, leadsize);
        if (trailsize != 0)
            pages_unmap((void *)((uintptr_t)ret + size), trailsize);
        return (ret);
    }
}
    |<-------------alloc_size---------->|
    +-----------+-----   --+------------+
    | lead_size | size...  | trail_size |
    +-----------+-----   --+------------+
    ^           ^
    |           |
    pages      ret(alignment)

static void *
chunk_alloc_mmap_slow(size_t size, size_t alignment, bool *zero)
{
    ......
    alloc_size = size + alignment - PAGE;
    if (alloc_size < size)
        return (NULL);
    do {
        pages = pages_map(NULL, alloc_size);
        if (pages == NULL)
            return (NULL);
        leadsize = ALIGNMENT_CEILING((uintptr_t)pages, alignment) -
            (uintptr_t)pages;
        ret = pages_trim(pages, alloc_size, leadsize, size);
    } while (ret == NULL);
    ......
    return (ret);
}

static void *
pages_trim(void *addr, size_t alloc_size, size_t leadsize, size_t size)
{
    void *ret = (void *)((uintptr_t)addr + leadsize);
    ......
    {
        size_t trailsize = alloc_size - leadsize - size;

        if (leadsize != 0)
            pages_unmap(addr, leadsize);
        if (trailsize != 0)
            pages_unmap((void *)((uintptr_t)ret + size), trailsize);
        return (ret);
    }
}

 

 

 

 

 

 

—-[ 3.4 –
Small allocation (tcache)

—-[ 3.4 –
Small allocation (tcache)

tcache内分配按照先easy后hard的方式. easy方式直接打tcache
bin的avail-stack
吃收获可用region. 如果tbin耗尽, 使用hard方式, 先refill avail-stack,
再实行
easy分配.

tcache内分配按照先easy后hard的方式. easy方式直接从tcache
bin的avail-stack
蒙拿走可用region. 如果tbin耗尽, 使用hard方式, 先refill avail-stack,
再实行
easy分配.

JEMALLOC_ALWAYS_INLINE void *
tcache_alloc_small(tcache_t *tcache, size_t size, bool zero)
{
    ......
    // xf: 先从tcache bin尝试分配
    ret = tcache_alloc_easy(tbin);
    // xf: 如果尝试失败, 则refill tcache bin, 并尝试分配
    if (ret == NULL) {
        ret = tcache_alloc_small_hard(tcache, tbin, binind);
        if (ret == NULL)
            return (NULL);
    }
    ......

    // xf: 执行tcache event
    tcache_event(tcache);
    return (ret);
}

JEMALLOC_ALWAYS_INLINE void *
tcache_alloc_easy(tcache_bin_t *tbin)
{
    void *ret;

    // xf: 如果tcache bin耗尽, 更新水线为-1
    if (tbin->ncached == 0) {
        tbin->low_water = -1;
        return (NULL);
    }
    // xf: pop栈顶的region, 如果需要更新水线
    tbin->ncached--;
    if ((int)tbin->ncached < tbin->low_water)
        tbin->low_water = tbin->ncached;
    ret = tbin->avail[tbin->ncached];
    return (ret);
}

void *
tcache_alloc_small_hard(tcache_t *tcache, tcache_bin_t *tbin, size_t binind)
{
    void *ret;

    arena_tcache_fill_small(tcache->arena, tbin, binind,
        config_prof ? tcache->prof_accumbytes : 0);
    if (config_prof)
        tcache->prof_accumbytes = 0;
    ret = tcache_alloc_easy(tbin);

    return (ret);
}
JEMALLOC_ALWAYS_INLINE void *
tcache_alloc_small(tcache_t *tcache, size_t size, bool zero)
{
    ......
    // xf: 先从tcache bin尝试分配
    ret = tcache_alloc_easy(tbin);
    // xf: 如果尝试失败, 则refill tcache bin, 并尝试分配
    if (ret == NULL) {
        ret = tcache_alloc_small_hard(tcache, tbin, binind);
        if (ret == NULL)
            return (NULL);
    }
    ......

    // xf: 执行tcache event
    tcache_event(tcache);
    return (ret);
}

JEMALLOC_ALWAYS_INLINE void *
tcache_alloc_easy(tcache_bin_t *tbin)
{
    void *ret;

    // xf: 如果tcache bin耗尽, 更新水线为-1
    if (tbin->ncached == 0) {
        tbin->low_water = -1;
        return (NULL);
    }
    // xf: pop栈顶的region, 如果需要更新水线
    tbin->ncached--;
    if ((int)tbin->ncached < tbin->low_water)
        tbin->low_water = tbin->ncached;
    ret = tbin->avail[tbin->ncached];
    return (ret);
}

void *
tcache_alloc_small_hard(tcache_t *tcache, tcache_bin_t *tbin, size_t binind)
{
    void *ret;

    arena_tcache_fill_small(tcache->arena, tbin, binind,
        config_prof ? tcache->prof_accumbytes : 0);
    if (config_prof)
        tcache->prof_accumbytes = 0;
    ret = tcache_alloc_easy(tbin);

    return (ret);
}

 

 

tcache fill同一般的arena bin分配类似. 首先, 获得同tbin相同index的arena
bin.
而后确定fill值, 该数值与2.7节牵线的lg_fill_div有关. 如果arena
run的runcur
可用则直接分配并push stack, 否则arena_bin_malloc_hard分配region.
push后的
逐条按从没有及大排列, 低地址之region更近乎栈顶位置.

tcache fill同普通的arena bin分配类似. 首先, 获得与tbin相同index的arena
bin.
事后确定fill值, 该数值及2.7节介绍的lg_fill_div有关. 如果arena
run的runcur
可用则直接分配并push stack, 否则arena_bin_malloc_hard分配region.
push后的
依次按自低及大排列, 低地址的region更贴近栈顶位置.

void
arena_tcache_fill_small(arena_t *arena, tcache_bin_t *tbin, size_t binind,
    uint64_t prof_accumbytes)
{
    ......
    // xf: 得到与tbin同index的arena bin
    bin = &arena->bins[binind];
    malloc_mutex_lock(&bin->lock);
    // xf: tbin的充满度与lg_fill_div相关
    for (i = 0, nfill = (tcache_bin_info[binind].ncached_max >>
        tbin->lg_fill_div); i < nfill; i++) {
        // xf: 如果current run可用, 则从中分配
        if ((run = bin->runcur) != NULL && run->nfree > 0)
            ptr = arena_run_reg_alloc(run, &arena_bin_info[binind]);
        else    // xf: current run耗尽, 则从bin中查找其他run分配
            ptr = arena_bin_malloc_hard(arena, bin);
        if (ptr == NULL)
            break;
        ......
        // xf: 低地址region优先放入栈顶
        tbin->avail[nfill - 1 - i] = ptr;
    }
    ......
    malloc_mutex_unlock(&bin->lock);
    // xf: 更新ncached
    tbin->ncached = i;
}
void
arena_tcache_fill_small(arena_t *arena, tcache_bin_t *tbin, size_t binind,
    uint64_t prof_accumbytes)
{
    ......
    // xf: 得到与tbin同index的arena bin
    bin = &arena->bins[binind];
    malloc_mutex_lock(&bin->lock);
    // xf: tbin的充满度与lg_fill_div相关
    for (i = 0, nfill = (tcache_bin_info[binind].ncached_max >>
        tbin->lg_fill_div); i < nfill; i++) {
        // xf: 如果current run可用, 则从中分配
        if ((run = bin->runcur) != NULL && run->nfree > 0)
            ptr = arena_run_reg_alloc(run, &arena_bin_info[binind]);
        else    // xf: current run耗尽, 则从bin中查找其他run分配
            ptr = arena_bin_malloc_hard(arena, bin);
        if (ptr == NULL)
            break;
        ......
        // xf: 低地址region优先放入栈顶
        tbin->avail[nfill - 1 - i] = ptr;
    }
    ......
    malloc_mutex_unlock(&bin->lock);
    // xf: 更新ncached
    tbin->ncached = i;
}

 

 

 

 

除此以外, 如2.7节所陈述, tcache在每次分配与放后都见面更新ev_cnt计数器.
当计数周期
达到TCACHE_GC_INCR时, 就见面启动tcache gc.
gc过程中会清理相当给low_water 3/4
数量的region,
并根据目前的low_water和lg_fill_div动态调整下一样浅refill时,
tbin的满度.

此外, 如2.7节所陈述, tcache在历次分配与自由后还见面更新ev_cnt计数器.
当计数周期
达到TCACHE_GC_INCR时, 就会启动tcache gc.
gc过程被见面清理相当给low_water 3/4
数之region,
并根据当下底low_water和lg_fill_div动态调整下同样蹩脚refill时,
tbin的充满度.

void
tcache_bin_flush_small(tcache_bin_t *tbin, size_t binind, unsigned rem,
    tcache_t *tcache)
{
    ......   
    // xf: 循环scan, 直到nflush为空.
    // 因为avail-stack中的region可能来自不同arena, 因此需要多次scan.
    // 每次scan将不同arena的region移动到栈顶, 留到下一轮scan时清理.
    for (nflush = tbin->ncached - rem; nflush > 0; nflush = ndeferred) {
        // xf: 获得栈顶region所属的arena和arena bin
        arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(
            tbin->avail[0]);
        arena_t *arena = chunk->arena;
        arena_bin_t *bin = &arena->bins[binind];
        ......
        // xf: 锁住栈顶region的arena bin
        malloc_mutex_lock(&bin->lock);
        ......
        // xf: ndefered代表所属不同arena的region被搬移的位置, 默认从0开始.
        // 本意是随着scan进行, nflush逐渐递增, nflush之前的位置空缺出来.
        // 当scan到不同arena region时, 将其指针移动到nflush前面的空缺中,
        // 留到下一轮scan, nflush重新开始. 直到ndefered和nflush重新为0.
        ndeferred = 0;
        for (i = 0; i < nflush; i++) {
            ptr = tbin->avail[i];
            chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
            // xf: 如果scan的region与栈顶region位于同一arena, 则释放,
            // 否则移动到ndefered标注的位置, 留到后面scan.
            if (chunk->arena == arena) {
                size_t pageind = ((uintptr_t)ptr -
                    (uintptr_t)chunk) >> LG_PAGE;
                arena_chunk_map_t *mapelm =
                    arena_mapp_get(chunk, pageind);
                ......
                // xf: 释放多余region
                arena_dalloc_bin_locked(arena, chunk, ptr,
                    mapelm);
            } else {
                tbin->avail[ndeferred] = ptr;
                ndeferred++;
            }
        }
        malloc_mutex_unlock(&bin->lock);
    }
    ......
    // xf: 将remainder regions指针移动到栈顶位置, 完成gc过程
    memmove(tbin->avail, &tbin->avail[tbin->ncached - rem],
        rem * sizeof(void *));
    // xf: 修正ncached以及low_water
    tbin->ncached = rem;
    if ((int)tbin->ncached < tbin->low_water)
        tbin->low_water = tbin->ncached;
}
void
tcache_bin_flush_small(tcache_bin_t *tbin, size_t binind, unsigned rem,
    tcache_t *tcache)
{
    ......   
    // xf: 循环scan, 直到nflush为空.
    // 因为avail-stack中的region可能来自不同arena, 因此需要多次scan.
    // 每次scan将不同arena的region移动到栈顶, 留到下一轮scan时清理.
    for (nflush = tbin->ncached - rem; nflush > 0; nflush = ndeferred) {
        // xf: 获得栈顶region所属的arena和arena bin
        arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(
            tbin->avail[0]);
        arena_t *arena = chunk->arena;
        arena_bin_t *bin = &arena->bins[binind];
        ......
        // xf: 锁住栈顶region的arena bin
        malloc_mutex_lock(&bin->lock);
        ......
        // xf: ndefered代表所属不同arena的region被搬移的位置, 默认从0开始.
        // 本意是随着scan进行, nflush逐渐递增, nflush之前的位置空缺出来.
        // 当scan到不同arena region时, 将其指针移动到nflush前面的空缺中,
        // 留到下一轮scan, nflush重新开始. 直到ndefered和nflush重新为0.
        ndeferred = 0;
        for (i = 0; i < nflush; i++) {
            ptr = tbin->avail[i];
            chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
            // xf: 如果scan的region与栈顶region位于同一arena, 则释放,
            // 否则移动到ndefered标注的位置, 留到后面scan.
            if (chunk->arena == arena) {
                size_t pageind = ((uintptr_t)ptr -
                    (uintptr_t)chunk) >> LG_PAGE;
                arena_chunk_map_t *mapelm =
                    arena_mapp_get(chunk, pageind);
                ......
                // xf: 释放多余region
                arena_dalloc_bin_locked(arena, chunk, ptr,
                    mapelm);
            } else {
                tbin->avail[ndeferred] = ptr;
                ndeferred++;
            }
        }
        malloc_mutex_unlock(&bin->lock);
    }
    ......
    // xf: 将remainder regions指针移动到栈顶位置, 完成gc过程
    memmove(tbin->avail, &tbin->avail[tbin->ncached - rem],
        rem * sizeof(void *));
    // xf: 修正ncached以及low_water
    tbin->ncached = rem;
    if ((int)tbin->ncached < tbin->low_water)
        tbin->low_water = tbin->ncached;
}

 

 

 

 

—-[ 3.5 – Large allocation

—-[ 3.5 – Large allocation

Arena上之large alloc同small相比除了省去arena bin的一些外,
并无本质区别.
着力算法如下,

Arena上之large alloc同small相比除了省去arena bin的有的以外,
并无本质区别.
主干算法如下,

  1. 把要大小对旅到page size上, 直接由avail-tree上找first-best-fit
    runs.
       如果成功, 则冲请求大小切割内存. 切割过程也同切割small run类似,
    区别在
       之后对chunk map的初始化不同. chunk map细节可溯2.3.3. 一旦失败,
    则进
       下一步.
  2. 不曾可用runs, 尝试创建new chunk, 成功同样切割run, 失败进入下一致步.
  3. 再尝试从avail-tree上追寻可用runs, 并返回.
  1. 管要大小对一头到page size上, 直接从avail-tree上摸first-best-fit
    runs.
       如果成功, 则基于请求大小切割内存. 切割过程吧与切割small run类似,
    区别在
       之后对chunk map的初始化不同. chunk map细节可溯2.3.3. 使失败,
    则上
       下一步.
  2. 靡可用runs, 尝试创建new chunk, 成功同样切割run, 失败进入下同样步.
  3. 又尝试从avail-tree上摸可用runs, 并返回.

跟地方的过程得看, 所谓large region分配一定给small run的分配.
区别仅
在于chunk map信息不同.

和地方的历程可见到, 所谓large region分配一定给small run的分配.
区别只
在chunk map信息不同.

Tcache上之large alloc同样以先easy后hard的顺序.
尽管常规arena上的分红不
有large bin, 但在tcache中却有large tbin,
因此仍然是先行查找avail-stack.
设tbin中搜寻不交, 就见面向arena申请large runs. 这里和small
alloc的区分在不
实践tbin refill, 因为考虑到过多large region的占用量问题. large
tbin仅在
tcache_dalloc_large的下才当募集region.
当tcache已满或GC周期至经常实行
tcache gc.

Tcache上之large alloc同样以先easy后hard的顺序.
尽管常规arena上的分红不
存large bin, 但在tcache中倒是存在large tbin,
因此还是是优先查找avail-stack.
倘若tbin中找不顶, 就会往arena申请large runs. 这里跟small
alloc的分在未
实行tbin refill, 因为考虑到了多large region的占用量问题. large
tbin仅在
tcache_dalloc_large的早晚才承受征集region.
当tcache已满或GC周期至经常实施
tcache gc.

—-[ 3.6 – Huge allocation

—-[ 3.6 – Huge allocation

Huge alloc相对于前就是越发简单. 因为对于Je而言, huge
region和chunk是均等的,
立在眼前来了叙述. Huge alloc就是调用chunk alloc,
并将extent_node记录在huge
tree上.

Huge alloc相对于眼前就是更简单. 因为对Je而言, huge
region和chunk是一样的,
立刻当前头来了叙述. Huge alloc就是调用chunk alloc,
并将extent_node记录在huge
tree上.

void *
huge_palloc(arena_t *arena, size_t size, size_t alignment, bool zero)
{
    void *ret;
    size_t csize;
    extent_node_t *node;
    bool is_zeroed;

    // xf: huge alloc对齐到chunksize
    csize = CHUNK_CEILING(size);
    ......
    // xf: create extent node以记录huge region
    node = base_node_alloc();
    ......
    arena = choose_arena(arena);
    // xf: 调用chunk alloc分配
    ret = arena_chunk_alloc_huge(arena, csize, alignment, &is_zeroed);
    // xf: 失败则清除extent node
    if (ret == NULL) {
        base_node_dalloc(node);
        return (NULL);
    }

    node->addr = ret;
    node->size = csize;
    node->arena = arena;

    // xf: 插入huge tree上
    malloc_mutex_lock(&huge_mtx);
    extent_tree_ad_insert(&huge, node);
    malloc_mutex_unlock(&huge_mtx);
    ......
    return (ret);
}
void *
huge_palloc(arena_t *arena, size_t size, size_t alignment, bool zero)
{
    void *ret;
    size_t csize;
    extent_node_t *node;
    bool is_zeroed;

    // xf: huge alloc对齐到chunksize
    csize = CHUNK_CEILING(size);
    ......
    // xf: create extent node以记录huge region
    node = base_node_alloc();
    ......
    arena = choose_arena(arena);
    // xf: 调用chunk alloc分配
    ret = arena_chunk_alloc_huge(arena, csize, alignment, &is_zeroed);
    // xf: 失败则清除extent node
    if (ret == NULL) {
        base_node_dalloc(node);
        return (NULL);
    }

    node->addr = ret;
    node->size = csize;
    node->arena = arena;

    // xf: 插入huge tree上
    malloc_mutex_lock(&huge_mtx);
    extent_tree_ad_insert(&huge, node);
    malloc_mutex_unlock(&huge_mtx);
    ......
    return (ret);
}

 

 

 

 

–[ 4 – Deallocation

–[ 4 – Deallocation

—-[ 4.1 – Overview

—-[ 4.1 – Overview

放活及分配过程倒, 按照一个由ptr -> run -> bin -> chunk ->
arena的路径.
不过以涉嫌page合并和purge, 实现更为复杂.
dalloc的输入从je_free -> ifree -> iqalloc -> iqalloct ->
idalloct.
对dalloc的解析从idalloct开始. 代码如下,

刑满释放和分配过程倒, 按照一个起ptr -> run -> bin -> chunk ->
arena的路程径.
但是因关乎page合并和purge, 实现更复杂.
dalloc的入口从je_free -> ifree -> iqalloc -> iqalloct ->
idalloct.
针对dalloc的解析由idalloct开始. 代码如下,

JEMALLOC_ALWAYS_INLINE void
idalloct(void *ptr, bool try_tcache)
{
    ......
    // xf: 获得被释放地址所在的chunk
    chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
    if (chunk != ptr)
        arena_dalloc(chunk, ptr, try_tcache);
    else
        huge_dalloc(ptr);
}
JEMALLOC_ALWAYS_INLINE void
idalloct(void *ptr, bool try_tcache)
{
    ......
    // xf: 获得被释放地址所在的chunk
    chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
    if (chunk != ptr)
        arena_dalloc(chunk, ptr, try_tcache);
    else
        huge_dalloc(ptr);
}

 

 

 

 

率先会检测为放指针ptr所在chunk的首地址与ptr是否一致, 如果是,
则一定也
huge region, 否则也small/large. 从这边分为arena和huge两漫长线.
双重拘留一下arena_dalloc,

首先会见检测为保释指针ptr所在chunk的首地址与ptr是否同样, 如果是,
则一定为
huge region, 否则也small/large. 从此处分为arena和huge两长长的线.
复看一下arena_dalloc,

JEMALLOC_ALWAYS_INLINE void
arena_dalloc(arena_chunk_t *chunk, void *ptr, bool try_tcache)
{
    ......
    // xf: 得到页面mapbits
    mapbits = arena_mapbits_get(chunk, pageind);

    if ((mapbits & CHUNK_MAP_LARGE) == 0) {
        if (try_tcache && (tcache = tcache_get(false)) != NULL) {
            // xf: ptr所在tcache的index
            binind = arena_ptr_small_binind_get(ptr, mapbits);
            tcache_dalloc_small(tcache, ptr, binind);
        } else
            arena_dalloc_small(chunk->arena, chunk, ptr, pageind);
    } else {
        size_t size = arena_mapbits_large_size_get(chunk, pageind);
        if (try_tcache && size <= tcache_maxclass && (tcache =
            tcache_get(false)) != NULL) {
            tcache_dalloc_large(tcache, ptr, size);
        } else
            arena_dalloc_large(chunk->arena, chunk, ptr);
    }
}
JEMALLOC_ALWAYS_INLINE void
arena_dalloc(arena_chunk_t *chunk, void *ptr, bool try_tcache)
{
    ......
    // xf: 得到页面mapbits
    mapbits = arena_mapbits_get(chunk, pageind);

    if ((mapbits & CHUNK_MAP_LARGE) == 0) {
        if (try_tcache && (tcache = tcache_get(false)) != NULL) {
            // xf: ptr所在tcache的index
            binind = arena_ptr_small_binind_get(ptr, mapbits);
            tcache_dalloc_small(tcache, ptr, binind);
        } else
            arena_dalloc_small(chunk->arena, chunk, ptr, pageind);
    } else {
        size_t size = arena_mapbits_large_size_get(chunk, pageind);
        if (try_tcache && size <= tcache_maxclass && (tcache =
            tcache_get(false)) != NULL) {
            tcache_dalloc_large(tcache, ptr, size);
        } else
            arena_dalloc_large(chunk->arena, chunk, ptr);
    }
}

 

 

 

 

这里通过获得ptr所在page的mapbits, 判断该自于small还是large. 然后再度
各自作处理.

这里通过取得ptr所在page的mapbits, 判断该根源于small还是large. 然后还
独家作处理.

之所以, 在dalloc一起来多分成了small/large/huge三漫漫途径执行. 事实上,
结前面的学问, large/huge可以看作run和chunk的特例. 所以, 这三久dalloc
途径最终会集聚至一同, 只需要为懂里边最为复杂的small region dalloc
虽得了.

故, 在dalloc一开始多分成了small/large/huge三漫漫路子执行. 事实上,
结前面的知, large/huge可以看作run和chunk的特例. 所以, 这三久dalloc
途径最终见面聚集至手拉手, 只待来明白里边最复杂的small region dalloc
纵使足以了.

不管small/large region, 都见面事先品尝释放回tcache,
不管其是否打tache中分红
假如来. 所谓tcache dalloc只不过是拿region记录在tbin中, 并无算是真的的释放.
只有有数种情景, 一是只要手上线程tbin已满, 会直接执行同样不好tbin flush,
释放出
有些tbin空间. 二是若tcache_event触发发了tache gc, 也会见履flush.
两者的
有别于在, 前者会回收指定tbin 1/2的空间,
而后者则放next_gc_bin相当于3/4
low water数量的空间.

无small/large region, 都见面事先品尝释放回tcache,
不管其是否从tache中分红
倘来. 所谓tcache dalloc只不过是用region记录在tbin中, 并无到底真的的释放.
惟有有数栽情景, 一凡是使手上线程tbin已满, 会直接执行同样糟tbin flush,
释放出
一些tbin空间. 二凡是要是tcache_event触发发了tache gc, 也会见实施flush.
两者的
别在于, 前者会回收指定tbin 1/2的长空,
而后者则放next_gc_bin相当于3/4
low water数量的空间.

JEMALLOC_ALWAYS_INLINE void
tcache_dalloc_small(tcache_t *tcache, void *ptr, size_t binind)
{
    ......
    tbin = &tcache->tbins[binind];
    tbin_info = &tcache_bin_info[binind];
    // xf: 如果当前tbin已满, 则执行flush清理tbin
    if (tbin->ncached == tbin_info->ncached_max) {
        tcache_bin_flush_small(tbin, binind, (tbin_info->ncached_max >>
            1), tcache);
    }
    // xf: 将被释放的ptr重新push进tbin
    tbin->avail[tbin->ncached] = ptr;
    tbin->ncached++;

    tcache_event(tcache);
}
JEMALLOC_ALWAYS_INLINE void
tcache_dalloc_small(tcache_t *tcache, void *ptr, size_t binind)
{
    ......
    tbin = &tcache->tbins[binind];
    tbin_info = &tcache_bin_info[binind];
    // xf: 如果当前tbin已满, 则执行flush清理tbin
    if (tbin->ncached == tbin_info->ncached_max) {
        tcache_bin_flush_small(tbin, binind, (tbin_info->ncached_max >>
            1), tcache);
    }
    // xf: 将被释放的ptr重新push进tbin
    tbin->avail[tbin->ncached] = ptr;
    tbin->ncached++;

    tcache_event(tcache);
}

 

 

 

 

tcache gc和tcache flush在2.7以及3.4节备受都介绍, 不再赘述.

tcache gc和tcache flush在2.7同3.4节面临曾经介绍, 不再赘述.

—-[ 4.2 – arena_dalloc_bin

—-[ 4.2 – arena_dalloc_bin

small region dalloc的第一步是尝试以region返还给所属的bin.
首要之步调就是是
基于用户传入的ptr推算出那所在run的地址.

small region dalloc的率先步是尝尝将region返还给所属的bin.
首要的步子就是是
依据用户传入的ptr推算出该所在run的地址.

run addr = chunk base + run page offset << LG_PAGE

run addr = chunk base + run page offset << LG_PAGE

一经run page offset根据2.3.3小节的说明,
可以通过ptr所在page的mapbits获得.

只要run page offset根据2.3.3小节的辨证,
可以透过ptr所在page的mapbits获得.

run page offset = ptr page index – ptr page offset

run page offset = ptr page index – ptr page offset

获得run后就是愈加将到所属的bin, 接着对bin加锁并回收, 如下,

收获run后就更用到所属的bin, 接着对bin加锁并回收, 如下,

void
arena_dalloc_bin(arena_t *arena, arena_chunk_t *chunk, void *ptr,
    size_t pageind, arena_chunk_map_t *mapelm)
{
    ......
    // xf: 计算ptr所在run地址.     
    run = (arena_run_t *)((uintptr_t)chunk + (uintptr_t)((pageind -
        arena_mapbits_small_runind_get(chunk, pageind)) << LG_PAGE));
    bin = run->bin;

    malloc_mutex_lock(&bin->lock);
    arena_dalloc_bin_locked(arena, chunk, ptr, mapelm);
    malloc_mutex_unlock(&bin->lock);
}
void
arena_dalloc_bin(arena_t *arena, arena_chunk_t *chunk, void *ptr,
    size_t pageind, arena_chunk_map_t *mapelm)
{
    ......
    // xf: 计算ptr所在run地址.     
    run = (arena_run_t *)((uintptr_t)chunk + (uintptr_t)((pageind -
        arena_mapbits_small_runind_get(chunk, pageind)) << LG_PAGE));
    bin = run->bin;

    malloc_mutex_lock(&bin->lock);
    arena_dalloc_bin_locked(arena, chunk, ptr, mapelm);
    malloc_mutex_unlock(&bin->lock);
}

 

 

 

 

lock的内容才是用region在run内部的bitmap上记为可用. bitmap unset的
经过此处省略, 请参考3.3.1小节中分红算法的解释. 与tcache dalloc类似,
日常情况下region并无见面真正释放. 但假如run内部任何乎空闲region, 则会
愈触发run的释放.

lock的情节就是拿region在run内部的bitmap上号为可用. bitmap unset的
经过此处省略, 请参考3.3.1小节中分配算法的解释. 与tcache dalloc类似,
万般状态下region并无见面真正释放. 但如果run内部尽也空闲region, 则会
一发触发run的释放.

void
arena_dalloc_bin_locked(arena_t *arena, arena_chunk_t *chunk,
void *ptr,
    arena_chunk_map_t *mapelm)
{
    ……    
    // xf: 通过run回收region, 在bitmap上重新标记region可用.
    arena_run_reg_dalloc(run, ptr);
    
    // xf: 如果那个所在run完全free, 则尝试释放该run.
    // 如果所在run处在将载状态(因为正的获释腾出一个region的半空中),
    // 则因地点高低优先将其交换到current run的职(MRU).
    if (run->nfree == bin_info->nregs) {
        arena_dissociate_bin_run(chunk, run, bin);
        arena_dalloc_bin_run(arena, chunk, run, bin);
    } else if (run->nfree == 1 && run != bin->runcur)
        arena_bin_lower_run(arena, chunk, run, bin);
    ……
}

void
arena_dalloc_bin_locked(arena_t *arena, arena_chunk_t *chunk,
void *ptr,
    arena_chunk_map_t *mapelm)
{
    ……    
    // xf: 通过run回收region, 在bitmap上重复标记region可用.
    arena_run_reg_dalloc(run, ptr);
    
    // xf: 如果其所在run完全free, 则尝试释放该run.
    // 如果所在run处在用满状态(因为刚的自由腾出一个region的半空中),
    // 则根据地点高低优先将那个交换到current run的职(MRU).
    if (run->nfree == bin_info->nregs) {
        arena_dissociate_bin_run(chunk, run, bin);
        arena_dalloc_bin_run(arena, chunk, run, bin);
    } else if (run->nfree == 1 && run != bin->runcur)
        arena_bin_lower_run(arena, chunk, run, bin);
    ……
}

另外还有平等栽情景是, 如果原先run本来是充满的,
因为前面的假释多生一个悠闲位置,
便见面尝试和current run交换位置. 若当前run比current run地址更不比,
会替代后者
连成为新的current run, 这样的利显而易见好保没有地址的内存更不方便实.

另外还有一样种情况是, 如果原先run本来是满载之,
因为前面的自由多发生一个空位置,
纵然会尝试与current run交换位置. 若当前run比current run地址更小,
会替代后者
并化新的current run, 这样的益处显而易见可以包没有地址之内存更艰难实.

static void
arena_bin_lower_run(arena_t *arena, arena_chunk_t *chunk,
arena_run_t *run,
    arena_bin_t *bin)
{
    if ((uintptr_t)run < (uintptr_t)bin->runcur) {
        if (bin->runcur->nfree > 0)
            arena_bin_runs_insert(bin, bin->runcur);
        bin->runcur = run;
        if (config_stats)
            bin->stats.reruns++;
    } else
        arena_bin_runs_insert(bin, run);
}

static void
arena_bin_lower_run(arena_t *arena, arena_chunk_t *chunk,
arena_run_t *run,
    arena_bin_t *bin)
{
    if ((uintptr_t)run < (uintptr_t)bin->runcur) {
        if (bin->runcur->nfree > 0)
            arena_bin_runs_insert(bin, bin->runcur);
        bin->runcur = run;
        if (config_stats)
            bin->stats.reruns++;
    } else
        arena_bin_runs_insert(bin, run);
}

普普通通情况下, 至此一个small region就释放了了, 准确的便是回收了.
但只要前方
所说, 若整个run都也空闲region, 则进入run dalloc.
这是一个比较复杂的过程.

通常状态下, 至此一个small region就自由了了, 准确之身为回收了.
但万一前
所说, 若整个run都也空闲region, 则进run dalloc.
这是一个比较复杂的过程.

—-[ 4.3 – small run dalloc

—-[ 4.3 – small run dalloc

一个non-full的small run被记录在bin内之run tree上, 因此只要移除它,
首先要移除
其于run tree中之音讯, 即arena_dissociate_bin_run.

一个non-full的small run被记录在bin内的run tree上, 因此一旦移除它,
首先使移除
那个当run tree中的音信, 即arena_dissociate_bin_run.

static void
arena_dissociate_bin_run(arena_chunk_t *chunk, arena_run_t
*run,
    arena_bin_t *bin)
{
    // xf: 如果手上run为current run, 清除runcur. 否则, 从run
tree上remove.
    if (run == bin->runcur)
        bin->runcur = NULL;
    else {
        ……
        if (bin_info->nregs != 1) {
            arena_bin_runs_remove(bin, run);
        }
    }
}

static void
arena_dissociate_bin_run(arena_chunk_t *chunk, arena_run_t
*run,
    arena_bin_t *bin)
{
    // xf: 如果手上run为current run, 清除runcur. 否则, 从run
tree上remove.
    if (run == bin->runcur)
        bin->runcur = NULL;
    else {
        ……
        if (bin_info->nregs != 1) {
            arena_bin_runs_remove(bin, run);
        }
    }
}

接下去要经arena_dalloc_bin_run()正式释放run, 由于经过稍复杂,
这里先给出不折不扣
算法的大意,

对接下要经过arena_dalloc_bin_run()正式释放run, 由于经过稍复杂,
这里先给出全部
算法的大校,

  1. 计算nextind region所在page的index. 所谓nextind是run内部clean-dirty
    region
       的边界. 如果中存在clean pages则执行下同样步, 否则实施3.
  2. 将旧之small run转化成large run,
    之后据悉达一致步得到的nextind将run切割成
       dirty和clean两片段, 且单独放掉clean部分.   
  3. 将待remove的run pages标记为unalloc.
    且根据传入的dirty和cleaned两只hint
       决定号后的page mapbits的dirty flag.
  4. 检查unalloc后底run pages是否可以上下合并. 合并的正统是,
       1) 不超过chunk范围
       2) 前后毗邻的page同样为unalloc
       3) 前后毗邻page的dirty flag与run pages相同.
  5. 以合并后(也可能没有统一)的unalloc run插入avail-tree.
  6. 自我批评如果unalloc run的轻重相等chunk size, 则将chunk释放掉.
  7. 倘前释放run pages为dirty, 则检查时arena内部的dirty-active
    pages比例.
       若dirty数量超过了active的1/8(Android这里的规范有所不同), 则启动arena
    purge.
       否则一直回回.
  8. 算算时arena可以清理的dirty pages数量npurgatory.
  9. 打dirty tree上各个取出dirty chunk, 并检查中的unalloc dirty pages,
    将其
       重新分配为large pages, 并插入到临时的queue中.
  10. 本着临时队列中之dirty pages执行purge, 返回值为unzeroed标记. 再以purged
    pages
        的unzeroed标记设置同一不折不扣.
  11. 终极对负有purged pages重新履行同一总体dalloc run操作,
    将其还放出回avail-tree.
  1. 计算nextind region所在page的index. 所谓nextind是run内部clean-dirty
    region
       的边界. 如果中间存在clean pages则履行下一样步, 否则执行3.
  2. 拿原始之small run转化成large run,
    之后根据上一致步得到的nextind将run切割成
       dirty和clean两片, 且单独放掉clean部分.   
  3. 将待remove的run pages标记为unalloc.
    且根据传入的dirty和cleaned两只hint
       决定号后的page mapbits的dirty flag.
  4. 检查unalloc后底run pages是否可上下合并. 合并的业内是,
       1) 不超过chunk范围
       2) 前后毗邻的page同样为unalloc
       3) 前后毗邻page的dirty flag与run pages相同.
  5. 以合并后(也可能无统一)的unalloc run插入avail-tree.
  6. 检查如果unalloc run的轻重等chunk size, 则将chunk释放掉.
  7. 假使前释放run pages为dirty, 则检查时arena内部的dirty-active
    pages比例.
       若dirty数量过了active的1/8(Android这里的正规有所不同), 则启动arena
    purge.
       否则一直归回.
  8. 计时arena可以清理的dirty pages数量npurgatory.
  9. 自打dirty tree上各个取出dirty chunk, 并检查中的unalloc dirty pages,
    将那
       重新分配为large pages, 并插入到现之queue中.
  10. 本着临时队列中之dirty pages执行purge, 返回值为unzeroed标记. 再用purged
    pages
        的unzeroed标记设置同一全.
  11. 最终对富有purged pages重新履行同一合dalloc run操作,
    将其重释放回avail-tree.

可以看出, 释放run本质上是以那个回收至avail-tree. 但附加的dirty
page机制却长了
尽算法的复杂程度. 原因就在, Je使用了不同以往底内存释放方式.

好望, 释放run本质上是将该回收至avail-tree. 但附加的dirty
page机制却有增无减了
任何算法的复杂程度. 原因即在于, Je使用了不同以往的内存释放方式.

以Dl这样的经典分配器中, 系统内存回收措施更加”古板”.
比如当heap区需top-most
space存在大于某个threshold的接连free空间时才会开展auto-trimming.
而mmap区则
再也使赶有segment全部悠然才能够实行munmap.
这对于回收体系内存是多不利的,
为条件过于严格.

每当Dl这样的经文分配器中, 系统内存回收措施更为”古板”.
比如以heap区要top-most
space存在大于某个threshold的连free空间时才能够进行auto-trimming.
而mmap区则
双重要等到有segment全部空余才会行munmap.
这对回收体系内存是远不利的,
因口径过于严格.

若是Je使用了越发聪明之章程, 并无见面直接交还系统内存,
而是通过madvise暂时释放掉
页面和物理页面内的映射.
本质上立和sbrk/munmap之类的调用要达的目的是接近之,
只不过由过程之中的角度看, 该地点仍然被占用.
但Je对这些应用了之地址都详细做了
笔录, 因此再度分配时可recycle, 并无会见导致对线性地址无停歇的开采.

如若Je使用了逾聪明的法, 并无见面一直交还系统内存,
而是通过madvise暂时释放掉
页面及物理页面中的映射.
本质上立即和sbrk/munmap之类的调用要达成的目的是看似的,
只不过由过程中的角度看, 该地方仍然被占用.
但Je对这些使用过的地点都详细做了
记录, 因此还分配时好recycle, 并无会见招对线性地址无终止的开采.

此外, 为了增强对已经放page的利用率, Je将unalloc pages用dirty flag(注意,
这里
及page replacement中之含义不同)做了符号(参考2.3.3节省被chunkmapbits).
所有pages
为分成active, dirty和clean三种. dirty pages表示早已使了,
且仍可能涉及着物理
页面, recycle速度比快. 而clean则意味着没有利用,
或都由此purge释放了物理页面,
比较前者速度慢. 显然, 需要一致种内置算法来维系三栽page的动态平衡,
以兼顾分配速度
暨内存占用量. 如果手上dirty pages数量过了active pages数量的
1/2^opt_lg_dirty_mult, 就见面启动arena_purge(). 这个值默认是1/8,
如下,

此外, 为了加强对就出狱page的利用率, Je将unalloc pages用dirty flag(注意,
这里
及page replacement中的意义不同)做了标记(参考2.3.3节中chunkmapbits).
所有pages
给分成active, dirty和clean三种. dirty pages表示早已采用了,
且仍可能波及着物理
页面, recycle速度比较快. 而clean则意味没有利用,
或早已由此purge释放了物理页面,
于前者速度慢. 显然, 需要平等栽内置算法来保障三种植page的动态平衡,
以兼职分配速度
暨内存占用量. 如果手上dirty pages数量过了active pages数量之
1/2^opt_lg_dirty_mult, 就会见启动arena_purge(). 这个值默认是1/8,
如下,

static inline void
arena_maybe_purge(arena_t *arena)
{
    ……
    // xf: 如果手上dirty pages全部当尽purging, 则直接回到回.
    if (arena->ndirty <= arena->npurgatory)
        return;
        
    // xf: 检查purageable pages是否超出active-dirty比率, 超出则
    // 执行purge. google在此处多了ANDROID_ALWAYS_PURGE开关,
    // 打开则总会执行arena_purge(默认是开辟的).
#if !defined(ANDROID_ALWAYS_PURGE)
    npurgeable = arena->ndirty – arena->npurgatory;
    threshold = (arena->nactive >> opt_lg_dirty_mult);
    if (npurgeable <= threshold)
        return;
#endif

static inline void
arena_maybe_purge(arena_t *arena)
{
    ……
    // xf: 如果手上dirty pages全部每当履purging, 则直接回到回.
    if (arena->ndirty <= arena->npurgatory)
        return;
        
    // xf: 检查purageable pages是否超出active-dirty比率, 超出则
    // 执行purge. google在这里多了ANDROID_ALWAYS_PURGE开关,
    // 打开则总会执行arena_purge(默认是打开的).
#if !defined(ANDROID_ALWAYS_PURGE)
    npurgeable = arena->ndirty – arena->npurgatory;
    threshold = (arena->nactive >> opt_lg_dirty_mult);
    if (npurgeable <= threshold)
        return;
#endif

    // xf: 执行purge
    arena_purge(arena, false);
}

    // xf: 执行purge
    arena_purge(arena, false);
}

唯独google显然想对dirty pages管理更严峻一些, 以适应移动装备上内存
偏偏小之题目. 这里多了一个ALWAYS_PURGE的开关, 打开后会强制每次释放
不时都执行arena_purge.

而google显然想对dirty pages管理更严厉一些, 以适应移动装备上内存
偏偏小之题材. 这里多了一个ALWAYS_PURGE的开关, 打开后会强制每次释放
不时还施行arena_purge.

arena_run_dalloc代码如下,
static void
arena_run_dalloc(arena_t *arena, arena_run_t *run, bool dirty,
bool cleaned)
{
    ……
    // xf: 如果run pages的dirty flag实际读取为true, 且cleaned不呢true,
    // 则无异于以为该pages在dalloc后是dirty的,
否则让视为clean(该情形适用于
    // chunk purge后, 重新dalloc时, 此时的run pages虽然dirty
flag可能为ture,
    // 但由此purge后当改为clean).
    if (cleaned == false && arena_mapbits_dirty_get(chunk, run_ind)
!= 0)
        dirty = true;
    flag_dirty = dirty ? CHUNK_MAP_DIRTY : 0;

arena_run_dalloc代码如下,
static void
arena_run_dalloc(arena_t *arena, arena_run_t *run, bool dirty,
bool cleaned)
{
    ……
    // xf: 如果run pages的dirty flag实际读取为true, 且cleaned不也true,
    // 则同样以为该pages在dalloc后是dirty的,
否则让视为clean(该情况适用于
    // chunk purge后, 重新dalloc时, 此时的run pages虽然dirty
flag可能为ture,
    // 但经purge后该改也clean).
    if (cleaned == false && arena_mapbits_dirty_get(chunk, run_ind)
!= 0)
        dirty = true;
    flag_dirty = dirty ? CHUNK_MAP_DIRTY : 0;

    // xf: 将被remove的run标记为unalloc pages. 前面的判定如果是dirty,
则pages
    // mapbits以包含dirty flag, 否则用非带有dirty flag.
    if (dirty) {
        arena_mapbits_unallocated_set(chunk, run_ind, size,
            CHUNK_MAP_DIRTY);
        arena_mapbits_unallocated_set(chunk, run_ind+run_pages-1,
size,
            CHUNK_MAP_DIRTY);
    } else {
        arena_mapbits_unallocated_set(chunk, run_ind, size,
            arena_mapbits_unzeroed_get(chunk, run_ind));
        arena_mapbits_unallocated_set(chunk, run_ind+run_pages-1,
size,
            arena_mapbits_unzeroed_get(chunk,
run_ind+run_pages-1));
    }

    // xf: 将被remove的run标记为unalloc pages. 前面的论断如果是dirty,
则pages
    // mapbits用含有dirty flag, 否则拿无带有dirty flag.
    if (dirty) {
        arena_mapbits_unallocated_set(chunk, run_ind, size,
            CHUNK_MAP_DIRTY);
        arena_mapbits_unallocated_set(chunk, run_ind+run_pages-1,
size,
            CHUNK_MAP_DIRTY);
    } else {
        arena_mapbits_unallocated_set(chunk, run_ind, size,
            arena_mapbits_unzeroed_get(chunk, run_ind));
        arena_mapbits_unallocated_set(chunk, run_ind+run_pages-1,
size,
            arena_mapbits_unzeroed_get(chunk,
run_ind+run_pages-1));
    }

    // xf: 尝试以吃remove run与上下unalloc pages 合并.
    arena_run_coalesce(arena, chunk, &size, &run_ind, &run_pages,
        flag_dirty);
    ……
    
    // xf: 将执行了联合后底run重新insert到avail-tree
    arena_avail_insert(arena, chunk, run_ind, run_pages, true,
true);

    // xf: 尝试用被remove run与上下unalloc pages 合并.
    arena_run_coalesce(arena, chunk, &size, &run_ind, &run_pages,
        flag_dirty);
    ……
    
    // xf: 将执行过联合后底run重新insert到avail-tree
    arena_avail_insert(arena, chunk, run_ind, run_pages, true,
true);

    // xf: 检查如果统一后底size已经完全unallocated, 则dalloc整个chunk
    if (size == arena_maxclass) {
        ……
        arena_chunk_dalloc(arena, chunk);
    }
    if (dirty)
        arena_maybe_purge(arena);
}

    // xf: 检查如果统一后的size已经完全unallocated, 则dalloc整个chunk
    if (size == arena_maxclass) {
        ……
        arena_chunk_dalloc(arena, chunk);
    }
    if (dirty)
        arena_maybe_purge(arena);
}

coalesce代码如下,
static void
arena_run_coalesce(arena_t *arena, arena_chunk_t *chunk, size_t
*p_size,
    size_t *p_run_ind, size_t *p_run_pages, size_t
flag_dirty)
{
    ……
    // xf: 尝试同后面的pages合并
    if (run_ind + run_pages < chunk_npages &&
        arena_mapbits_allocated_get(chunk, run_ind+run_pages) == 0
&&
        arena_mapbits_dirty_get(chunk, run_ind+run_pages) ==
flag_dirty) {
        size_t nrun_size =
arena_mapbits_unallocated_size_get(chunk,
            run_ind+run_pages);
        size_t nrun_pages = nrun_size >> LG_PAGE;
        ……
        // xf: 如果与后的unalloc pages合并, remove
page时后的adjacent
        // hint应为true
        arena_avail_remove(arena, chunk, run_ind+run_pages,
nrun_pages,
            false, true);

coalesce代码如下,
static void
arena_run_coalesce(arena_t *arena, arena_chunk_t *chunk, size_t
*p_size,
    size_t *p_run_ind, size_t *p_run_pages, size_t
flag_dirty)
{
    ……
    // xf: 尝试和后的pages合并
    if (run_ind + run_pages < chunk_npages &&
        arena_mapbits_allocated_get(chunk, run_ind+run_pages) == 0
&&
        arena_mapbits_dirty_get(chunk, run_ind+run_pages) ==
flag_dirty) {
        size_t nrun_size =
arena_mapbits_unallocated_size_get(chunk,
            run_ind+run_pages);
        size_t nrun_pages = nrun_size >> LG_PAGE;
        ……
        // xf: 如果跟背后的unalloc pages合并, remove
page时后的adjacent
        // hint应为true
        arena_avail_remove(arena, chunk, run_ind+run_pages,
nrun_pages,
            false, true);

        size += nrun_size;
        run_pages += nrun_pages;

        size += nrun_size;
        run_pages += nrun_pages;

        arena_mapbits_unallocated_size_set(chunk, run_ind, size);
        arena_mapbits_unallocated_size_set(chunk,
run_ind+run_pages-1, size);
    }

        arena_mapbits_unallocated_size_set(chunk, run_ind, size);
        arena_mapbits_unallocated_size_set(chunk,
run_ind+run_pages-1, size);
    }

    // xf: 尝试和前面的pages合并
    if (run_ind > map_bias &&
arena_mapbits_allocated_get(chunk,
        run_ind-1) == 0 && arena_mapbits_dirty_get(chunk,
run_ind-1) ==
        flag_dirty) {
        ……
    }

    // xf: 尝试与前方的pages合并
    if (run_ind > map_bias &&
arena_mapbits_allocated_get(chunk,
        run_ind-1) == 0 && arena_mapbits_dirty_get(chunk,
run_ind-1) ==
        flag_dirty) {
        ……
    }

    *p_size = size;
    *p_run_ind = run_ind;
    *p_run_pages = run_pages;
}

    *p_size = size;
    *p_run_ind = run_ind;
    *p_run_pages = run_pages;
}

avail-tree remove代码如下,
static void
arena_avail_remove(arena_t *arena, arena_chunk_t *chunk, size_t
pageind,
    size_t npages, bool maybe_adjac_pred, bool maybe_adjac_succ)
{
    ……
    // xf: 该调用可能用造成chunk内部的碎片化率改变, 从而影响其当dirty
tree
    // 中之排序. 因此, 在专业remove之前用以chunk首先由dirty
tree中remove,
    // 待更新中ndirty后, 再用该再insert回dirty tree.
    if (chunk->ndirty != 0)
        arena_chunk_dirty_remove(&arena->chunks_dirty, chunk);

avail-tree remove代码如下,
static void
arena_avail_remove(arena_t *arena, arena_chunk_t *chunk, size_t
pageind,
    size_t npages, bool maybe_adjac_pred, bool maybe_adjac_succ)
{
    ……
    // xf: 该调用可能用致chunk内部的碎片化率改变, 从而影响其当dirty
tree
    // 中的排序. 因此, 在正儿八经remove之前要用chunk首先由dirty
tree中remove,
    // 待更新中ndirty后, 再用该又insert回dirty tree.
    if (chunk->ndirty != 0)
        arena_chunk_dirty_remove(&arena->chunks_dirty, chunk);

    // xf: maybe_adjac_pred/succ是外流传的hint,
根据该值检查前后是否存在
    // clean-dirty边界. 若有边界, 则remove avail pages后边界将削弱1.
    if (maybe_adjac_pred && arena_avail_adjac_pred(chunk,
pageind))
        chunk->nruns_adjac–;
    if (maybe_adjac_succ && arena_avail_adjac_succ(chunk, pageind,
npages))
        chunk->nruns_adjac–;
    chunk->nruns_avail–;
    ……

    // xf: maybe_adjac_pred/succ是外面盛传的hint,
根据该值检查前后是否存在
    // clean-dirty边界. 若存在边界, 则remove avail pages后边界将减弱1.
    if (maybe_adjac_pred && arena_avail_adjac_pred(chunk,
pageind))
        chunk->nruns_adjac–;
    if (maybe_adjac_succ && arena_avail_adjac_succ(chunk, pageind,
npages))
        chunk->nruns_adjac–;
    chunk->nruns_avail–;
    ……

    // xf: 更新arena及chunk中dirty pages统计.
    if (arena_mapbits_dirty_get(chunk, pageind) != 0) {
        arena->ndirty -= npages;
        chunk->ndirty -= npages;
    }
    // xf: 如果chunk内部dirty不为0, 将其再次insert到arena dirty tree.
    if (chunk->ndirty != 0)
        arena_chunk_dirty_insert(&arena->chunks_dirty, chunk);

    // xf: 更新arena及chunk中dirty pages统计.
    if (arena_mapbits_dirty_get(chunk, pageind) != 0) {
        arena->ndirty -= npages;
        chunk->ndirty -= npages;
    }
    // xf: 如果chunk内部dirty不为0, 将那个再insert到arena dirty tree.
    if (chunk->ndirty != 0)
        arena_chunk_dirty_insert(&arena->chunks_dirty, chunk);

    // xf: 从chunk avail-tree中remove掉unalloc pages.
    arena_avail_tree_remove(&arena->runs_avail,
arena_mapp_get(chunk,
        pageind));
}

    // xf: 从chunk avail-tree中remove掉unalloc pages.
    arena_avail_tree_remove(&arena->runs_avail,
arena_mapp_get(chunk,
        pageind));
}

自从avail-tree上remove pages可能会见改目前chunk内部clean-dirty碎片率,
因此
一样开始如以那个所在chunk从dirty tree上remove, 再由avail-tree上remove
pages.
另外, arena_avail_insert()的算法同remove是一致的, 只是趋势相反,
不再赘述.

起avail-tree上remove pages可能会见改变目前chunk内部clean-dirty碎片率,
因此
如出一辙开始要将该所在chunk从dirty tree上remove, 再起avail-tree上remove
pages.
另外, arena_avail_insert()的算法同remove是均等的, 只是趋势相反,
不再赘述.

—-[ 4.4 – arena purge    

—-[ 4.4 – arena purge    

清理arena的艺术是比照自小至十分之一一遍历一株dirty tree, 直到将dirty
pages降低
暨threshold以下. dirty tree挂载所有dirty chunks,
同其他tree的别在她的cmp
函数较突出, 决定了最终之purging order, 如下,

清理arena的章程是据自小至死的各个遍历一蔸dirty tree, 直到将dirty
pages降低
到threshold以下. dirty tree挂载所有dirty chunks,
同其他tree的区别在她的cmp
函数较突出, 决定了最终之purging order, 如下,

static inline int
arena_chunk_dirty_comp(arena_chunk_t *a, arena_chunk_t *b)
{
    ……
    if (a == b)
        return (0);

static inline int
arena_chunk_dirty_comp(arena_chunk_t *a, arena_chunk_t *b)
{
    ……
    if (a == b)
        return (0);

    {
        size_t a_val = (a->nruns_avail – a->nruns_adjac) *
            b->nruns_avail;
        size_t b_val = (b->nruns_avail – b->nruns_adjac) *
            a->nruns_avail;

    {
        size_t a_val = (a->nruns_avail – a->nruns_adjac) *
            b->nruns_avail;
        size_t b_val = (b->nruns_avail – b->nruns_adjac) *
            a->nruns_avail;

        if (a_val < b_val)
            return (1);
        if (a_val > b_val)
            return (-1);
    }
    {
        uintptr_t a_chunk = (uintptr_t)a;
        uintptr_t b_chunk = (uintptr_t)b;
        int ret = ((a_chunk > b_chunk) – (a_chunk <
b_chunk));
        if (a->nruns_adjac == 0) {
            assert(b->nruns_adjac == 0);
            ret = -ret;
        }
        return (ret);
    }
}

        if (a_val < b_val)
            return (1);
        if (a_val > b_val)
            return (-1);
    }
    {
        uintptr_t a_chunk = (uintptr_t)a;
        uintptr_t b_chunk = (uintptr_t)b;
        int ret = ((a_chunk > b_chunk) – (a_chunk <
b_chunk));
        if (a->nruns_adjac == 0) {
            assert(b->nruns_adjac == 0);
            ret = -ret;
        }
        return (ret);
    }
}

Je于此给出底算法是这么的,

Je在这边叫闹之算法是这般的,

  1. 首先败short cut, 即a及b相同的特例.
  2. 计算a, b的fragmentation, 该数值越强, 相应的在dirty tree上即一发靠前.
       其计算办法为,
       
       当前平均avail run大小    所有avail run数量 – 边界数量
       ——————— =  —————————–
        去碎片后底平均大小           所有avail run数量
        
       注意, 这个fragment不是司空见惯意义理解的碎片. 这里依由clean-dirty
       边界形成的所谓碎片, 并且是得经purge清除掉的, 如图,
  1. 首先排除short cut, 即a以及b相同的特例.
  2. 计算a, b的fragmentation, 该数值越强, 相应的在dirty tree上就进一步靠前.
       其计算方式也,
       
       当前平均avail run大小    所有avail run数量 – 边界数量
       ——————— =  —————————–
        去碎片后底平均大小           所有avail run数量
        
       注意, 这个fragment不是普通意义理解的碎片. 这里依由clean-dirty
       边界形成的所谓碎片, 并且是好透过purge清除掉的, 如图,

   nruns_adjac = 2    
  
+——–+———-+——–+——-+———+———-+——–+—–
   | dirty  |  clean   |        | clean |  dirty  |          | dirty  |

  
+——–+———-+——–+——-+———+———-+——–+—–
            ^                           ^
            |                           |
            +–adjac #0                 +–adjac #1

   nruns_adjac = 2    
  
+——–+———-+——–+——-+———+———-+——–+—–
   | dirty  |  clean   |        | clean |  dirty  |          | dirty  |

  
+——–+———-+——–+——-+———+———-+——–+—–
            ^                           ^
            |                           |
            +–adjac #0                 +–adjac #1

  1. 当a, b的fragmentation相同时, 同通常的章程类似, 按地址大小排序. 但若
       nruns_adjac为0, 即不存clean-dirty边界时,
    反而会将低地址chunk排至后面.
       因为adjac为0的chunk再采取价值是比强的,
    所以放到后面可以加其于purge
       中的依存几率领, 从而提升recycle效率.
  1. 当a, b的fragmentation相同时, 同通常的办法类似, 按地址大小排序. 但万一
       nruns_adjac为0, 即不存在clean-dirty边界时,
    反而会将低地址chunk排至后面.
       因为adjac为0的chunk再用价值是于大的,
    所以放到后面可以追加其于purge
       中的幸存几带领, 从而提升recycle效率.

此处要说明的凡, Je这个cmp函数私家觉得如有题目,
实际跟踪代码也发现该并
匪可知重新优先purge高碎片率的chunk. 但同那个自我证实并未得到信服的说明.
但当下套
算法就在3.x版本被中, 在新型的4.x中虽净摒弃了现有的回收算法.

此处要证明的是, Je这个cmp函数私家觉得似乎有题目,
实际跟踪代码也发现该并
勿可知还优先purge高碎片率的chunk. 但同那个自我征并未得到信服的说明.
但这套
算法就在3.x版本中中, 在新型的4.x中则完全摒弃了现有的回收算法.

purge代码如下,
static void
arena_purge(arena_t *arena, bool all)
{
    ……
    // xf: 计算purgeable pages, 结果在到npurgatory信息中.
    npurgatory = arena_compute_npurgatory(arena, all);
    arena->npurgatory += npurgatory;

purge代码如下,
static void
arena_purge(arena_t *arena, bool all)
{
    ……
    // xf: 计算purgeable pages, 结果在到npurgatory信息中.
    npurgatory = arena_compute_npurgatory(arena, all);
    arena->npurgatory += npurgatory;

    // xf: 从dirty chunk tree上逐chunk执行purge,
直到期望值npurgatory为0
    while (npurgatory > 0) {
        ……
        chunk = arena_chunk_dirty_first(&arena->chunks_dirty);
        // xf: traversal结束, 当前线程无法做到purge任务, 返回.
        if (chunk == NULL) {
            arena->npurgatory -= npurgatory;
            return;
        }
        npurgeable = chunk->ndirty;
        ……
        // xf:  如果手上chunk中purgeable大于前期测算的purgatory,
        // 且该clean-dirty碎片为0, 则让眼前线程负责purge所有prgeable
pages.
        // 原因是以尽量避免避免多独线程对拖欠chunk的purge竞争.
        if (npurgeable > npurgatory && chunk->nruns_adjac == 0)
{
            arena->npurgatory += npurgeable – npurgatory;
            npurgatory = npurgeable;
        }
        arena->npurgatory -= npurgeable;
        npurgatory -= npurgeable;
        npurged = arena_chunk_purge(arena, chunk, all);
        // xf: 计算purge期望值npurgatory和实际purge值npurged差值
        nunpurged = npurgeable – npurged;
        arena->npurgatory += nunpurged;
        npurgatory += nunpurged;
    }
}

    // xf: 从dirty chunk tree上逐chunk执行purge,
直到期望值npurgatory为0
    while (npurgatory > 0) {
        ……
        chunk = arena_chunk_dirty_first(&arena->chunks_dirty);
        // xf: traversal结束, 当前线程无法做到purge任务, 返回.
        if (chunk == NULL) {
            arena->npurgatory -= npurgatory;
            return;
        }
        npurgeable = chunk->ndirty;
        ……
        // xf:  如果手上chunk中purgeable大于前期测算的purgatory,
        // 且该clean-dirty碎片为0, 则让眼前线程负责purge所有prgeable
pages.
        // 原因是为尽可能避免避免多独线程对该chunk的purge竞争.
        if (npurgeable > npurgatory && chunk->nruns_adjac == 0)
{
            arena->npurgatory += npurgeable – npurgatory;
            npurgatory = npurgeable;
        }
        arena->npurgatory -= npurgeable;
        npurgatory -= npurgeable;
        npurged = arena_chunk_purge(arena, chunk, all);
        // xf: 计算purge期望值npurgatory和实际purge值npurged差值
        nunpurged = npurgeable – npurged;
        arena->npurgatory += nunpurged;
        npurgatory += nunpurged;
    }
}

chunk purge如下,
static inline size_t
arena_chunk_purge(arena_t *arena, arena_chunk_t *chunk, bool
all)
{
    ……
    if (chunk == arena->spare) {
        ……
        arena_chunk_alloc(arena);
    }
    ……
    // xf: 为了减小arena purge时arena lock的中断时间, 先将有着满足
    // 需求的unalloc dirty pages重新”alloc”并保留, 待purge结束还又
    // 释放回avail-tree.
    arena_chunk_stash_dirty(arena, chunk, all, &mapelms);
    npurged = arena_chunk_purge_stashed(arena, chunk, &mapelms);
    arena_chunk_unstash_purged(arena, chunk, &mapelms);

chunk purge如下,
static inline size_t
arena_chunk_purge(arena_t *arena, arena_chunk_t *chunk, bool
all)
{
    ……
    if (chunk == arena->spare) {
        ……
        arena_chunk_alloc(arena);
    }
    ……
    // xf: 为了减小arena purge时arena lock的中断时间, 先将所有满足
    // 需求的unalloc dirty pages重新”alloc”并保存, 待purge结束再重复
    // 释放回avail-tree.
    arena_chunk_stash_dirty(arena, chunk, all, &mapelms);
    npurged = arena_chunk_purge_stashed(arena, chunk, &mapelms);
    arena_chunk_unstash_purged(arena, chunk, &mapelms);

    return (npurged);
}

    return (npurged);
}

chunk purge重点在于这是一个线性查找dirty pages过程, Je在这边会见招致性
下降. 更不好之是, 之前和后来还是当arena lock被锁定的原则下为实践, 绑定
同一arena的线程不得不终止工作. 因此, 在正规purge前需要先管unalloc
dirty
pages全部临时分配出来, 当purging时解锁arena lock, 而结束后再行同不行将她
全方位释放.

chunk purge重点在于这是一个线性查找dirty pages过程, Je在此地会造成性
下降. 更糟糕的凡, 之前与后都是以arena lock被锁定的极下被执行, 绑定
同一arena的线程不得不偃旗息鼓工作. 因此, 在业内purge前需要事先拿unalloc
dirty
pages全部现分配出来, 当purging时解锁arena lock, 而结束晚更同破以其
一体释放.

stash dirty代码,
static void
arena_chunk_stash_dirty(arena_t *arena, arena_chunk_t *chunk,
bool all,
    arena_chunk_mapelms_t *mapelms)
{
    ……
    for (pageind = map_bias; pageind < chunk_npages; pageind +=
npages) {
        arena_chunk_map_t *mapelm = arena_mapp_get(chunk,
pageind);
        if (arena_mapbits_allocated_get(chunk, pageind) == 0) {
            ……
            if (arena_mapbits_dirty_get(chunk, pageind) != 0 &&
                (all || arena_avail_adjac(chunk, pageind,
                npages))) {
                arena_run_t *run = (arena_run_t *)((uintptr_t)
                    chunk + (uintptr_t)(pageind << LG_PAGE));
                // xf: 暂时将这些unalloc dirty pages通过split large
                // 重新分配出来.                    
                arena_run_split_large(arena, run, run_size,
                    false);
                // 加入临时列表, 留待后用.    
                ql_elm_new(mapelm, u.ql_link);
                ql_tail_insert(mapelms, mapelm, u.ql_link);
            }
        } else {    
            //xf: 跳过allocated pages
            ……
        }
    }
    ……
}

stash dirty代码,
static void
arena_chunk_stash_dirty(arena_t *arena, arena_chunk_t *chunk,
bool all,
    arena_chunk_mapelms_t *mapelms)
{
    ……
    for (pageind = map_bias; pageind < chunk_npages; pageind +=
npages) {
        arena_chunk_map_t *mapelm = arena_mapp_get(chunk,
pageind);
        if (arena_mapbits_allocated_get(chunk, pageind) == 0) {
            ……
            if (arena_mapbits_dirty_get(chunk, pageind) != 0 &&
                (all || arena_avail_adjac(chunk, pageind,
                npages))) {
                arena_run_t *run = (arena_run_t *)((uintptr_t)
                    chunk + (uintptr_t)(pageind << LG_PAGE));
                // xf: 暂时拿这些unalloc dirty pages通过split large
                // 重新分配出来.                    
                arena_run_split_large(arena, run, run_size,
                    false);
                // 加入临时列表, 留待后用.    
                ql_elm_new(mapelm, u.ql_link);
                ql_tail_insert(mapelms, mapelm, u.ql_link);
            }
        } else {    
            //xf: 跳过allocated pages
            ……
        }
    }
    ……
}

stash时会根据传入的hint all判断, 如果为false, 只会stash存在clean-dirty
adjac的pages, 否则会漫入列表.

stash时会根据传入的hint all判断, 如果为false, 只会stash存在clean-dirty
adjac的pages, 否则会尽加入列表.

purge stashed pages代码如下,
static size_t
arena_chunk_purge_stashed(arena_t *arena, arena_chunk_t
*chunk,
    arena_chunk_mapelms_t *mapelms)
{
    ……
    // xf: 暂时解锁arena lock, 前面已经realloc过,
这里不考虑contention问题.
    malloc_mutex_unlock(&arena->lock);
    ……
    ql_foreach(mapelm, mapelms, u.ql_link) {
        ……
        // xf: 逐个purge dirty page, 返回pages是否unzeroed.
        unzeroed = pages_purge((void *)((uintptr_t)chunk + (pageind
<<
            LG_PAGE)), (npages << LG_PAGE));
        flag_unzeroed = unzeroed ? CHUNK_MAP_UNZEROED : 0;
        
        // xf: 逐pages设置unzeroed标志.
        for (i = 0; i < npages; i++) {
            arena_mapbits_unzeroed_set(chunk, pageind+i,
                flag_unzeroed);
        }
        ……
    }
    // xf: purging结束还lock arena
    malloc_mutex_lock(&arena->lock);
    ……
    return (npurged);
}

purge stashed pages代码如下,
static size_t
arena_chunk_purge_stashed(arena_t *arena, arena_chunk_t
*chunk,
    arena_chunk_mapelms_t *mapelms)
{
    ……
    // xf: 暂时解锁arena lock, 前面早已realloc过,
这里不考虑contention问题.
    malloc_mutex_unlock(&arena->lock);
    ……
    ql_foreach(mapelm, mapelms, u.ql_link) {
        ……
        // xf: 逐个purge dirty page, 返回pages是否unzeroed.
        unzeroed = pages_purge((void *)((uintptr_t)chunk + (pageind
<<
            LG_PAGE)), (npages << LG_PAGE));
        flag_unzeroed = unzeroed ? CHUNK_MAP_UNZEROED : 0;
        
        // xf: 逐pages设置unzeroed标志.
        for (i = 0; i < npages; i++) {
            arena_mapbits_unzeroed_set(chunk, pageind+i,
                flag_unzeroed);
        }
        ……
    }
    // xf: purging结束还lock arena
    malloc_mutex_lock(&arena->lock);
    ……
    return (npurged);
}

此处而顾的是, 在page purge过后, 会逐一设置unzero flag. 这是因有点
操作系统在demand page后会见有平等步zero-fill-on-demand. 因此, 被purge过之
clean page当再同软提请及大体页面时见面全体填写为0.

这边而留意的凡, 在page purge过后, 会逐一设置unzero flag. 这是盖有些
操作系统在demand page后会生平等步zero-fill-on-demand. 因此, 被purge过的
clean page当再同不善申请到大体页面时见面漫填写为0.

unstash代码,
static void
arena_chunk_unstash_purged(arena_t *arena, arena_chunk_t
*chunk,
    arena_chunk_mapelms_t *mapelms)
{
    ……
    for (mapelm = ql_first(mapelms); mapelm != NULL;
        mapelm = ql_first(mapelms)) {
        ……
        run = (arena_run_t *)((uintptr_t)chunk +
(uintptr_t)(pageind <<
            LG_PAGE));
        ql_remove(mapelms, mapelm, u.ql_link);
        arena_run_dalloc(arena, run, false, true);
    }
}

unstash代码,
static void
arena_chunk_unstash_purged(arena_t *arena, arena_chunk_t
*chunk,
    arena_chunk_mapelms_t *mapelms)
{
    ……
    for (mapelm = ql_first(mapelms); mapelm != NULL;
        mapelm = ql_first(mapelms)) {
        ……
        run = (arena_run_t *)((uintptr_t)chunk +
(uintptr_t)(pageind <<
            LG_PAGE));
        ql_remove(mapelms, mapelm, u.ql_link);
        arena_run_dalloc(arena, run, false, true);
    }
}

unstash需要再同蹩脚调用arena_run_dalloc()以释放临时分配的pages. 要注意
此时咱们既身处arena_run_dalloc调用栈中, 而避免太递归重入依靠参数
cleaned flag.

unstash需要再行同差调用arena_run_dalloc()以自由临时分配的pages. 要留意
这咱们已放在arena_run_dalloc调用栈中, 而避免太递归重入依靠参数
cleaned flag.

—-[ 4.5 – arena chunk dalloc

—-[ 4.5 – arena chunk dalloc

当free chunk被Je释放时, 根据局部性原理, 会成为下一个spare
chunk而保存起来,
该肉体并未排散. 而本的spare则会根据其中dalloc方法让处理掉.

当free chunk被Je释放时, 根据局部性原理, 会成为下一个spare
chunk而保存起来,
夫躯体并未打消散. 而原来的spare则会因其中dalloc方法吃处理掉.

static void
arena_chunk_dalloc(arena_t *arena, arena_chunk_t *chunk)
{
    ……
    // xf: 将chunk从avail-tree上remove
    arena_avail_remove(arena, chunk, map_bias,
chunk_npages-map_bias,
        false, false);

static void
arena_chunk_dalloc(arena_t *arena, arena_chunk_t *chunk)
{
    ……
    // xf: 将chunk从avail-tree上remove
    arena_avail_remove(arena, chunk, map_bias,
chunk_npages-map_bias,
        false, false);

    // xf: 如果spare不为空, 则将为保释的chunk替换原spare chunk.
    if (arena->spare != NULL) {
        arena_chunk_t *spare = arena->spare;

    // xf: 如果spare不为空, 则将于放出的chunk替换原spare chunk.
    if (arena->spare != NULL) {
        arena_chunk_t *spare = arena->spare;

        arena->spare = chunk;
        arena_chunk_dalloc_internal(arena, spare);
    } else
        arena->spare = chunk;
}

        arena->spare = chunk;
        arena_chunk_dalloc_internal(arena, spare);
    } else
        arena->spare = chunk;
}

以及chunk alloc一样, chunk dalloc算法也是可定制的. Je提供的默认算法
chunk_dalloc_default最终见面调用chunk_unmap, 如下,

与chunk alloc一样, chunk dalloc算法也是可定制的. Je提供的默认算法
chunk_dalloc_default最终会调用chunk_unmap, 如下,

void
chunk_unmap(void *chunk, size_t size)
{
    ……
    // xf: 如果启用dss, 且当前chunk在dss内, 将其record在dss tree上.
    // 否则一经就记下在mmap tree上, 或者直接munmap释放掉.
    if (have_dss && chunk_in_dss(chunk))
        chunk_record(&chunks_szad_dss, &chunks_ad_dss, chunk,
size);
    else if (chunk_dalloc_mmap(chunk, size))
        chunk_record(&chunks_szad_mmap, &chunks_ad_mmap, chunk,
size);
}

void
chunk_unmap(void *chunk, size_t size)
{
    ……
    // xf: 如果启用dss, 且当前chunk在dss内, 将该record在dss tree上.
    // 否则要就记下在mmap tree上, 或者直接munmap释放掉.
    if (have_dss && chunk_in_dss(chunk))
        chunk_record(&chunks_szad_dss, &chunks_ad_dss, chunk,
size);
    else if (chunk_dalloc_mmap(chunk, size))
        chunk_record(&chunks_szad_mmap, &chunks_ad_mmap, chunk,
size);
}

于3.3.5有些节中alloc时会基于dss和mmap优先履recycle.
源自在dalloc时record
每当四棵chunk tree上的记录. 但跟spare记录之例外,
这里的记录就只剩余躯壳,
record时会粗暴释放物理页面, 因此recycle速度比spare较慢.

在3.3.5略带节中alloc时会依据dss和mmap优先履recycle.
源自在dalloc时record
以四棵chunk tree上之记录. 但和spare记录的两样,
这里的笔录就不过剩余躯壳,
record时会见粗暴释放物理页面, 因此recycle速度比spare较慢.

chunk record算法如下,

chunk record算法如下,

  1. 先purge chunk内部装有pages
  2. 预分配base node, 以记录释放后的chunk.
    这里分配的node到背后可能无就此,
       提前分配是盖接下去要加锁chunks_mtx. 而只要以薄段内再分配base
    node,
       则恐以base pages不足而申请新的chunk, 这样一来就会造成dead lock.
  3. 寻找与如插入chunk的分界地址. 首先尝试与背后的地方合并, 成功则就此后世
       的base node记录, 之后执行5.
  4. 合破产, 用预分配的base node记录chunk.
  5. 品味和前方的地点合并.
  6. 万一预分配的base node没有应用, 释放掉.
  1. 先purge chunk内部有pages
  2. 预分配base node, 以记录释放后底chunk.
    这里分配的node到背后可能没因此,
       提前分配是坐属下去要加锁chunks_mtx. 而如果在压段内再分配base
    node,
       则恐为base pages不足而申请新的chunk, 这样一来就见面促成dead lock.
  3. 搜寻跟如插入chunk的交界地址. 首先尝试和后的地点合并, 成功则据此后世
       的base node记录, 之后执行5.
  4. 合并破产, 用预分配的base node记录chunk.
  5. 品尝同前面的地方合并.
  6. 假设预分配的base node没有用, 释放掉.

代码如下,
static void
chunk_record(extent_tree_t *chunks_szad, extent_tree_t
*chunks_ad, void *chunk,
    size_t size)
{
    ……
    // xf: purge all chunk pages
    unzeroed = pages_purge(chunk, size);

代码如下,
static void
chunk_record(extent_tree_t *chunks_szad, extent_tree_t
*chunks_ad, void *chunk,
    size_t size)
{
    ……
    // xf: purge all chunk pages
    unzeroed = pages_purge(chunk, size);

    // xf: 预先分配extent_node以记录chunk. 如果该chunk可以展开联合,
该node
    // 可能连无见面采用. 这里预先分配要是避免dead lock. 因为一些情况
    // base_node_alloc同样可能会alloc base chunk, 由于后面chunk
mutex被lock,
    // 那样以招致dead lock.
    xnode = base_node_alloc();
    xprev = NULL;

    // xf: 预先分配extent_node以记录chunk. 如果该chunk可以拓展联,
该node
    // 可能连无见面利用. 这里预先分配要是避免dead lock. 因为一些情况
    // base_node_alloc同样可能会alloc base chunk, 由于后面chunk
mutex被lock,
    // 那样将导致dead lock.
    xnode = base_node_alloc();
    xprev = NULL;

    malloc_mutex_lock(&chunks_mtx);
    // xf: 首先尝试同后面的chunk合并.
    key.addr = (void *)((uintptr_t)chunk + size);
    node = extent_tree_ad_nsearch(chunks_ad, &key);

    malloc_mutex_lock(&chunks_mtx);
    // xf: 首先尝试和后的chunk合并.
    key.addr = (void *)((uintptr_t)chunk + size);
    node = extent_tree_ad_nsearch(chunks_ad, &key);

    if (node != NULL && node->addr == key.addr) {
        extent_tree_szad_remove(chunks_szad, node);
        node->addr = chunk;
        node->size += size;
        node->zeroed = (node->zeroed && (unzeroed == false));
        extent_tree_szad_insert(chunks_szad, node);
    } else {    
        // xf: 合并失败, 用提前分配好之xnode保存时chunk信息.
        if (xnode == NULL) {
            goto label_return;
        }
        node = xnode;
        xnode = NULL;
        node->addr = chunk;
        node->size = size;
        node->zeroed = (unzeroed == false);
        extent_tree_ad_insert(chunks_ad, node);
        extent_tree_szad_insert(chunks_szad, node);
    }

    if (node != NULL && node->addr == key.addr) {
        extent_tree_szad_remove(chunks_szad, node);
        node->addr = chunk;
        node->size += size;
        node->zeroed = (node->zeroed && (unzeroed == false));
        extent_tree_szad_insert(chunks_szad, node);
    } else {    
        // xf: 合并失败, 用提前分配好之xnode保存时chunk信息.
        if (xnode == NULL) {
            goto label_return;
        }
        node = xnode;
        xnode = NULL;
        node->addr = chunk;
        node->size = size;
        node->zeroed = (unzeroed == false);
        extent_tree_ad_insert(chunks_ad, node);
        extent_tree_szad_insert(chunks_szad, node);
    }

    // xf: 再尝试和眼前的chunk合并
    prev = extent_tree_ad_prev(chunks_ad, node);
    if (prev != NULL && (void *)((uintptr_t)prev->addr +
prev->size) ==
        chunk) {
        ……
    }

    // xf: 再尝试与前的chunk合并
    prev = extent_tree_ad_prev(chunks_ad, node);
    if (prev != NULL && (void *)((uintptr_t)prev->addr +
prev->size) ==
        chunk) {
        ……
    }

label_return:
    malloc_mutex_unlock(&chunks_mtx);
    // xf: 如果先分配的node没有使用, 则以是将的销毁
    if (xnode != NULL)
        base_node_dalloc(xnode);
    if (xprev != NULL)
        base_node_dalloc(xprev);
}

label_return:
    malloc_mutex_unlock(&chunks_mtx);
    // xf: 如果先分配的node没有采用, 则当斯将之销毁
    if (xnode != NULL)
        base_node_dalloc(xnode);
    if (xprev != NULL)
        base_node_dalloc(xprev);
}

最终顺带一提, 对于mmap区的pages, Je也可以直接munmap, 前提是亟需以
jemalloc_internal_defs.h中开启JEMALLOC_MUNMAP, 这样即使不见面执行pages
purge.
默认该选择是勿被之. 但源自dss区中之分红则无有反朝自由一说,
默认Je也
勿见面优先挑选dss就是了.

末尾顺带一提, 对于mmap区的pages, Je也可一直munmap, 前提是需要在
jemalloc_internal_defs.h中开启JEMALLOC_MUNMAP, 这样就是未会见履行pages
purge.
默认该选择是不开之. 但源自dss区中的分红则无有反为自由一说,
默认Je也
莫见面预先选项dss就是了.

bool
chunk_dalloc_mmap(void *chunk, size_t size)
{

bool
chunk_dalloc_mmap(void *chunk, size_t size)
{

    if (config_munmap)
        pages_unmap(chunk, size);

    if (config_munmap)
        pages_unmap(chunk, size);

    return (config_munmap == false);
}

    return (config_munmap == false);
}

—-[ 4.6 – large/huge dalloc

—-[ 4.6 – large/huge dalloc

前面说罢large/huge相当于为run和chunk为粒度的特例.
故此对于arena dalloc large来说, 最终便是arena_run_dalloc,
void
arena_dalloc_large_locked(arena_t *arena, arena_chunk_t *chunk,
void *ptr)
{

前说过large/huge相当于为run和chunk为粒度的特例.
用对于arena dalloc large来说, 最终便是arena_run_dalloc,
void
arena_dalloc_large_locked(arena_t *arena, arena_chunk_t *chunk,
void *ptr)
{

    if (config_fill || config_stats) {
        size_t pageind = ((uintptr_t)ptr – (uintptr_t)chunk) >>
LG_PAGE;
        size_t usize = arena_mapbits_large_size_get(chunk,
pageind);

    if (config_fill || config_stats) {
        size_t pageind = ((uintptr_t)ptr – (uintptr_t)chunk) >>
LG_PAGE;
        size_t usize = arena_mapbits_large_size_get(chunk,
pageind);

        arena_dalloc_junk_large(ptr, usize);
        if (config_stats) {
            arena->stats.ndalloc_large++;
            arena->stats.allocated_large -= usize;
            arena->stats.lstats[(usize >> LG_PAGE) –
1].ndalloc++;
            arena->stats.lstats[(usize >> LG_PAGE) –
1].curruns–;
        }
    }

        arena_dalloc_junk_large(ptr, usize);
        if (config_stats) {
            arena->stats.ndalloc_large++;
            arena->stats.allocated_large -= usize;
            arena->stats.lstats[(usize >> LG_PAGE) –
1].ndalloc++;
            arena->stats.lstats[(usize >> LG_PAGE) –
1].curruns–;
        }
    }

    arena_run_dalloc(arena, (arena_run_t *)ptr, true, false);
}

    arena_run_dalloc(arena, (arena_run_t *)ptr, true, false);
}

一旦huge dalloc, 则是当huge tree上摸, 最终实施chunk_dalloc,
void
huge_dalloc(void *ptr)
{
    ……
    malloc_mutex_lock(&huge_mtx);

若果huge dalloc, 则是在huge tree上探寻, 最终实施chunk_dalloc,
void
huge_dalloc(void *ptr)
{
    ……
    malloc_mutex_lock(&huge_mtx);

    key.addr = ptr;
    node = extent_tree_ad_search(&huge, &key);
    assert(node != NULL);
    assert(node->addr == ptr);
    extent_tree_ad_remove(&huge, node);

    key.addr = ptr;
    node = extent_tree_ad_search(&huge, &key);
    assert(node != NULL);
    assert(node->addr == ptr);
    extent_tree_ad_remove(&huge, node);

    malloc_mutex_unlock(&huge_mtx);

    malloc_mutex_unlock(&huge_mtx);

    huge_dalloc_junk(node->addr, node->size);
    arena_chunk_dalloc_huge(node->arena, node->addr,
node->size);
    base_node_dalloc(node);
}

    huge_dalloc_junk(node->addr, node->size);
    arena_chunk_dalloc_huge(node->arena, node->addr,
node->size);
    base_node_dalloc(node);
}

void
arena_chunk_dalloc_huge(arena_t *arena, void *chunk, size_t
size)
{
    chunk_dalloc_t *chunk_dalloc;

void
arena_chunk_dalloc_huge(arena_t *arena, void *chunk, size_t
size)
{
    chunk_dalloc_t *chunk_dalloc;

    malloc_mutex_lock(&arena->lock);
    chunk_dalloc = arena->chunk_dalloc;
    if (config_stats) {
        arena->stats.mapped -= size;
        arena->stats.allocated_huge -= size;
        arena->stats.ndalloc_huge++;
        stats_cactive_sub(size);
    }
    arena->nactive -= (size >> LG_PAGE);
    malloc_mutex_unlock(&arena->lock);
    chunk_dalloc(chunk, size, arena->ind);
}

    malloc_mutex_lock(&arena->lock);
    chunk_dalloc = arena->chunk_dalloc;
    if (config_stats) {
        arena->stats.mapped -= size;
        arena->stats.allocated_huge -= size;
        arena->stats.ndalloc_huge++;
        stats_cactive_sub(size);
    }
    arena->nactive -= (size >> LG_PAGE);
    malloc_mutex_unlock(&arena->lock);
    chunk_dalloc(chunk, size, arena->ind);
}

眼前都开了尽量介绍, 这里不再赘述.

前早已举行了充分介绍, 这里不再赘述.

—-[ 5 – 总结: 与Dl的对比

—-[ 5 – 总结: 与Dl的对比

  1. 只有核单线程分配能力及两者不相上下,
    甚至有点片内存分配速度理论及Dl还小占优势.
       原因是Dl利用双通往链表组织free chunk可以做到O(1),
    而尽管Je在bitmap上举行了自然
       优化, 但不可知成就常数时间.
       
  2. 多核多线程下, Je可以秒杀Dl. arena的投入既好避false sharing,
    又有何不可抽
       线程间lock contention. 另外,
    tcache也是可以大幅加快多线程分配速度的技术.
       这些Dl完全无有所竞争力.

  3. 网内存交换效率及吗是Je占明显优势.
    Je使用mmap/madvise的组合而比较Dl使用
       sbrk/mmap/munmap灵活之多. 实际对系统的下压力为又小. 另外,
    Dl使用dss->mmap,
       追求的凡速度, 而Je相反mmap->dss, 为的是活性.

  4. 有些片内存的散抑制上双方召开的且没错, 但总体达标个体觉得Je更好有的.
    首先
       dalloc时, 两者对空内存都可以实时coalesce.
    alloc时Dl依靠dv约束外部碎片,
       Je更简明暴力, 直接以固化的small runs里分配.    
       
       两互为较, dv的候选人是随机的, 大小非固定, 如果选择于粗的chunk,
    效果
       其实产生限. 更甚者, 当找不至dv时, Dl会自由切割top-most space,
    通常就不便民
       heap trim.
       
       而small runs则是定点大小, 同时是页面的平头倍,
    对表碎片的约束力和规整度上
       都更好.
       
       但Dl的优势于算法更简便, 速度更快. 无论是coalesce还是split代价都大低.
    在Je
       中来或为分红8byte之内存而实际去分配并初始化4k竟然4M的空间.

  5. 大块内存分配能力达到, Dl使用dst管理, 而Je采用rb tree. 原理及, 据说rb
    tree
       的cache亲和力较差, 不合乎memory allocator. 我没仔细研究Je的rb
    tree实现
       有哪过人之处, 暂且当各有千秋吧. 可以肯定的凡Je的large/huge
    region具有
       比Dl更强的内碎片, 皆因该重新规整的size class划分导致的.

  6. 说交size class, 可以见到Je的分割简明比Dl更仔细,
    tiny/small/large/huge四种
       分类能够兼顾更多之内存以模型. 且根据不同架构和安排,
    可以活变动划分方式,
       具有双重好之相当性. Dl划分的相对粗糙很多还较固定.
    一方面可能在就256byte
       以上就可以算大块的分红了吧. 另一方面某种程度是碍于算法的限制.
    比如当
       boundary tag中以容纳更多的信,
    就无可知小于8byte(实际可行之无限小chunk是
       16byte), bin数量不足多余31独为是冲位运算的方式.
       

  7. bookkeeping占用上Dl因为算法简单, 本应该占据更少内存. 但由boundary
    tag
       本身造成的占用, chunk数量越来越多, bookkeeping就越是大.
    再考虑到网回收效率上
       的劣势, 应该说, 应用内存占用越老, 尤其是略内存使用量越多,
    运行时刻越长,
       Dl相对于Je内存使用量倾向越来越大.

  8. 平安健壮性. 只说一样碰, boundary tag是原罪, 其他的得免谈了.

  1. 才核单线程分配能力达两者不相上下,
    甚至略片内存分配速度理论及Dl还稍占优势.
       原因是Dl利用双通向链表组织free chunk可以得O(1),
    而尽管Je在bitmap上做了定
       优化, 但不可知得常数时间.
       
  2. 多核多线程下, Je可以秒杀Dl. arena的进入既好免false sharing,
    又得减
       线程间lock contention. 另外,
    tcache也是足以大幅加快多线程分配速度之技术.
       这些Dl完全不有竞争力.

  3. 网内存交换效率及啊是Je占明显优势.
    Je使用mmap/madvise的做要比Dl使用
       sbrk/mmap/munmap灵活的多. 实际对系的压力呢还小. 另外,
    Dl使用dss->mmap,
       追求的是快, 而Je相反mmap->dss, 为的凡活性.

  4. 稍加片内存的零散抑制上两者做的且不错, 但总体达成个体觉得Je更好有的.
    首先
       dalloc时, 两者对空内存都可以实时coalesce.
    alloc时Dl依靠dv约束外部碎片,
       Je更简短暴力, 直接以固定的small runs里分配.    
       
       两交互较, dv的候选人是自由的, 大小不固定, 如果选择于小之chunk,
    效果
       其实产生限. 更甚者, 当找不顶dv时, Dl会随便切割top-most space,
    通常这不便宜
       heap trim.
       
       而small runs则是永恒大小, 同时是页面的整数倍增,
    对外表碎片的约束力和规整度上
       都更好.
       
       但Dl的优势在算法更简约, 速度又快. 无论是coalesce还是split代价都充分低.
    在Je
       中发生或以分红8byte之内存而事实上去分配并初始化4k竟是4M的空间.

  5. 大块内存分配能力及, Dl使用dst管理, 而Je采用rb tree. 原理上, 据说rb
    tree
       的cache亲和力较差, 不抱memory allocator. 我没仔细研究Je的rb
    tree实现
       有哪过人之处, 暂且觉得各出千秋吧. 可以得的是Je的large/huge
    region具有
       比Dl更胜之里碎片, 皆以其再次规整的size class划分导致的.

  6. 说交size class, 可以见到Je的划分简明比Dl更仔细,
    tiny/small/large/huge四种
       分类能够兼职更多的内存以模型. 且因不同架构和布置,
    可以灵活改变划分方式,
       具有更好之相当性. Dl划分的相对粗糙很多还比固定.
    一方面可能以当时256byte
       以上就得算作大块的分红了吧. 另一方面某种程度是碍于算法的限制.
    比如以
       boundary tag中为容纳更多之音讯,
    就无可知小于8byte(实际可行的无比小chunk是
       16byte), bin数量不足多余31只为是依据位运算的方式.
       

  7. bookkeeping占用上Dl因为算法简单, 本应该占据更少内存. 但由boundary
    tag
       本身造成的占据, chunk数量越来越多, bookkeeping就逾大.
    再考虑到网回收效率及
       的劣势, 应该说, 应用内存占用越怪, 尤其是聊内存使用量越多,
    运行时越来越长,
       Dl相对于Je内存使用量倾向越来越大.

  8. 平安健壮性. 只说一样点, boundary tag是原罪, 其他的足免谈了.

–[ 附: 快速调试Jemalloc

–[ 附: 快速调试Jemalloc

一个简便的调节Je的不二法门是盖静态库的方式将那编译到你的应用程序中.
先编译Je的静态库, 在源码目录下实施,

一个简的调节Je的主意是盖静态库的法门将该编译到你的应用程序中.
先编译Je的静态库, 在源码目录下实行,

./configure
make
make install

./configure
make
make install

即使可以编译并设置Je到网路径. 调试还得打开一些挑, 例如,

就是可编译并安装Je到系统路径. 调试还须打开一些摘, 例如,

./configure –enable-debug  –with-jemalloc-prefix=<prefix>

./configure –enable-debug  –with-jemalloc-prefix=<prefix>

这些选择之含义可以参考INSTALL文档. 比如,

这些选择之义可以参照INSTALL文档. 比如,

–disable-tcache 是否禁用tcache, 对调节非tcache流程有用.
–disable-prof   是否禁用heap profile.
–enable-debug   打开调试模式, 启动assert并关闭优化.
–with-jemalloc-prefix  将编译出之malloc加上设定的前缀,
以分别c库底调用.

–disable-tcache 是否禁用tcache, 对调剂非tcache流程有用.
–disable-prof   是否禁用heap profile.
–enable-debug   打开调试模式, 启动assert并关闭优化.
–with-jemalloc-prefix  将编译出底malloc加上设定的前缀,
以界别c库底调用.

从此以后就是可以用那编译到公的代码中, 如,

下虽好以那个编译到公的代码中, 如,

gcc main.c /usr/local/lib/libjemalloc.a -std=c99 -O0 -g3 -pthread -o
jhello

gcc main.c /usr/local/lib/libjemalloc.a -std=c99 -O0 -g3 -pthread -o
jhello

相关文章