jemalloc 3.6.0源码详解—[2]Chunk

转载自:vector03

2.3 Chunk (arena_chunk_t)

chunk是仅次于arena的次级内存结构. 如果有了解过dlmalloc, 就会知道在dlmalloc中同样定义了名为’chunk’的基础结构. 但这个概念在两个分配器中含义完全不同, dlmalloc中的chunk指代最低级分配单元, 而jemalloc中则是一个较大的内存区域.

2.3.1 overview

从前面arena的数据结构可以发现, 它是一个非常抽象的概念, 其大小也不代表实际的内存分配量. 原始的内存数据既非挂载在arena外部, 也并没有通过内部指针引用, 而是记录在chunk中. 按照一般的思路, chunk包含原始内存数据, 又从属于arena, 因此arena应该会有一个数组之类的结构以记录所有chunk信息. 但事实上同样找不到这样的记录. 那jemalloc又如何获得chunk指针呢?

所谓的chunk结构, 只是整个chunk的一个header, bookkeeping以及user memory都挂在header外面. 另外jemalloc对chunk又做了规定, 默认每个chunk大小为4MB, 同时还必须对齐到4MB的边界上.

#define    LG_CHUNK_DEFAULT    22

这个宏定义了chunk的大小. 注意到前缀’LG_’, 代表log即指数部分. jemalloc中所有该前缀的代码都是这个含义, 便于通过bit操作进行快速的运算.

有了上述规定, 获得chunk就变得几乎没有代价. 因为返回给user程序的内存地址肯定属于某个chunk, 而该chunk header对齐到4M边界上, 且不可能超过4M大小(即向上对齐到最近的4M整数倍地址), 所以只需要对该地址做一个下对齐就得到chunk指针, 如下,

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

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

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

以及上对齐到chunk边界的计算,

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

用图来表示如下,

这里写图片描述

###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]; 
}
  • arena: chunk属于哪个arena.

  • dirty_link: 用于arena chunks_dirty(rb tree)的链接节点. 如果某个chunk内部含有任何dirty page, 就会被挂载到arena中的chunks_dirty tree上.

  • ndirty: 内部dirty page数量.

  • nruns_avail: 内部available runs数量.

  • 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中.

这里写图片描述

至于由chunk header和chunk map占用的page数量, 保存在map_bias变量中. 该变量是jemalloc在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迭代回去, 得到最终大于第二次且小于第一次的计算结果.
相关代码如下,

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.2.3 chunk map (arena_chunk_map_t)

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

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

举例来说, jemalloc会把所有已分配run记录在内部rb tree上以快速检索, 实际地操作是将该run中第一个page对应的chunk_map作为rb node挂载到tree上. 检索时也是先找出将相应的chunk map, 再进行地址转换得到run的基址.

按照通常的设计思路, 我们可能会把run指针作为节点直接保存到rb tree(bins结构中的rb tree)中. 但jemalloc中的设计明显要更复杂. 究其原因, 如果把link node放到run中, 后果是bookkeeping和user memory将混淆在一起, 这对于分配器的安全性是很不利的. 包括dlmalloc在内的传统分配器都具有这样的缺陷. 而如果单独用link node记录run, 又会造成空间浪费. 正因为jemalloc中无论是chunk还是run都是连续page组成, 所以用首个page对应的chunk map就能同时代表该run的基址.

jemalloc中通常用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);
}

chunk map对page状态描述都压缩记录到bits中, 由于内容较多, 直接引用jemalloc代码
中的注释,

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

???????? ???????? ????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?

    下面是对三种类型的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

  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

  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 (sampled, size <= PAGE):
    ssssssss ssssssss ssssnnnn nnnnD-LA

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

为了提取/设置map bits内部的信息, jemalloc提供了一组函数, 这里列举两个最基本的,剩下的都是读取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)));
}

根据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]);
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
jQuery 是一个非常流行的 JavaScript 库,它简化了 JavaScript 代码的编写和操作 DOM 的过程。以下是 jQuery 源码的逐行详解: ```javascript (function( global, factory ) { "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { // 如果是 CommonJS 规范,则添加到模块中 // 在 Node.js 环境中,module 对象代表当前模块,exports 对象是 module 的属性,用于指定模块对外输出的接口。 // 通过 exports 对象把模块导出,其他文件就可以通过 require() 函数来引用该模块。 module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { // 如果是浏览器环境,则将 jQuery 添加到全局对象 window 中 factory( global ); } // 在这里传入了两个参数,global 和 factory,分别表示全局对象和一个工厂函数 }(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { "use strict"; var deletedIds = []; var document = window.document; var slice = deletedIds.slice; var concat = deletedIds.concat; var push = deletedIds.push; var indexOf = deletedIds.indexOf; // ... ``` 这段代码定义了一个立即执行函数,其目的是为了防止变量污染全局作用域。如果是在 Node.js 环境中,那么需要将 jQuery 添加到模块中,否则将 jQuery 添加到全局对象 window 中。然后定义了一些变量,包括 deletedIds、document、slice、concat、push 和 indexOf 等。这些变量都是为了后面的代码服务。 ```javascript var version = "3.6.0", // 定义了一个空函数 jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); }, // ... ``` 这里定义了 jQuery 的版本号和 jQuery 函数。jQuery 函数是一个工厂函数,用于创建 jQuery 对象,其实现是通过调用 jQuery.fn.init 函数实现的。 ```javascript jQuery.fn = jQuery.prototype = { // jQuery 对象的方法和属性 }; jQuery.fn.init.prototype = jQuery.fn; ``` 这里定义了 jQuery 对象的原型,包括 jQuery.fn 和 jQuery.prototype。jQuery.fn 和 jQuery.prototype 是等价的,都是为了扩展 jQuery 对象的方法和属性。同时还将 jQuery.fn.init 的原型指向了 jQuery.fn,这样可以让 jQuery.fn.init 实例化出来的对象拥有 jQuery.fn 的方法和属性。 ```javascript jQuery.extend = jQuery.fn.extend = function() { // ... }; jQuery.extend({ // 一些静态方法和属性 }); jQuery.fn.extend({ // 一些实例方法和属性 }); ``` 这里定义了 jQuery 的 extend 方法,用于实现对象的合并。同时还定义了一些静态方法和属性,以及一些实例方法和属性。 ```javascript jQuery.expr = { // ... }; jQuery.expr[ ":" ] = { // ... }; ``` 这里定义了 jQuery 的表达式引擎,用于实现选择器的解析。其中,jQuery.expr 是一个对象,存储了所有的选择器类型。jQuery.expr[ ":" ] 表示的是伪类选择器。 ```javascript jQuery.ajax = function( url, options ) { // ... }; jQuery.fn.load = function( url, params, callback ) { // ... }; ``` 这里定义了 jQuery 的 ajax 和 load 方法,用于实现异步请求和载入 HTML 片段。 ```javascript var // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$; jQuery.noConflict = function( deep ) { // ... }; if ( typeof noGlobal === "undefined" ) { window.jQuery = window.$ = jQuery; } ``` 这里定义了 jQuery 的 noConflict 方法,用于解决命名冲突问题。同时还保存了 window.jQuery 和 window.$ 的引用,以便在需要时恢复原来的状态。最后,将 jQuery 和 $ 添加到全局对象 window 中。 以上是 jQuery 源码的部分逐行详解,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值