jemalloc 3.6.0源码详解—[4]Thread caches

转载自:vector03

2.7 Thread caches (tcache_t)

TLS/TSD是另一种针对多线程优化使用的分配技术, jemalloc中称为tcache. tcache解决的是同一cpu core下不同线程对heap的竞争. 通过为每个线程指定专属分配区域,来减小线程间的干扰. 但显然这种方法会增大整体内存消耗量. 为了减小副作用,jemalloc将tcache设计成一个bookkeeping结构, 在tcache中保存的仅仅是指向外部region的指针, region对象仍然位于各个run当中. 换句话说, 如果一个region被tcache记录了, 那么从run的角度看, 它就已经被分配了.

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];    
};
  • link: 链接节点, 用于将同一个arena下的所有tcache链接起来.

  • prof_accumbytes: memory profile相关.

  • arena: 该tcache所属的arena指针.

  • ev_cnt: 是tcache内部的一个周期计数器. 每当tcache执行一次分配或释放时, ev_cnt会记录一次. 直到周期到来, jemalloc会执行一次incremental gc.这里的gc会清理tcache中多余的region, 将它们释放掉. 尽管这不意味着系统内存会获得释放, 但可以解放更多的region交给其他更饥饿的线程以分配.

  • next_gc_bin: 指向下一次gc的binidx. tcache gc按照一周期清理一个bin执行.

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

tcache bin结构如下,

struct tcache_bin_s {
    tcache_bin_stats_t tstats;
    int                   low_water;
    unsigned             lg_fill_div;
    unsigned             ncached;
    void                  **avail;
}
  • 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.

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

struct tcache_bin_info_s {
    unsigned    ncached_max;
};

tcache layout如下,

这里写图片描述

2.8 Extent Node (extent_node_t)

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

jemalloc中对所有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;
};
  • link_szad: szad tree的link节点.

  • link_ad: ad tree的link节点.

  • prof_ctx: 用于memory profile.

  • addr: 指向huge region的指针.

  • size: region size.

  • arena: huge region所属arena.

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

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

为了保证内部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);
}

与通常的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节点. 这里需要仔细体会这个强制类型转换的巧妙之处.

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值