jemalloc横向分析(四) tcache分配内存中使用到的位图bitmap

/*
 * Read-only information associated with each element of arena_t's bins array
 * is stored separately, partly to reduce memory usage (only one copy, rather
 * than one per arena), but mainly to avoid false cacheline sharing.
 * 和arena_t的bins数组的每个元素关联的只读信息,是单独存储的,部分目的是缩减内存使用,而不是每个area都拷贝一份
   主要是因为避免cache对齐共享错误
 * Each run has the following layout: 每个run(一个run可以理解成一片可使用的内存(large,多页,small,一页))都是如下布局
 *
 *               /--------------------\
 *               | pad?               |
 *               |--------------------|
 *               | redzone            |
 *   reg0_offset | region 0           |
 *               | redzone            |
 *               |--------------------| \
 *               | redzone            | |
 *               | region 1           |  > reg_interval
 *               | redzone            | /
 *               |--------------------|
 *               | ...                |
 *               | ...                |
 *               | ...                |
 *               |--------------------|
 *               | redzone            |
 *               | region nregs-1     |
 *               | redzone            |
 *               |--------------------|
 *               | alignment pad?     |
 *               \--------------------/
 *
 * reg_interval has at least the same minimum alignment as reg_size; this
 * preserves the alignment constraint that sa2u() depends on.  Alignment pad is
 * either 0 or redzone_size; it is present only if needed to align reg0_offset.
 */
reg_interval(reg间距)至少要按照reg_size的最小值对齐,sa2u依赖这一预留的对齐约束
对齐填充(alignment pad)可以是0或者redzone_size,仅在需要和reg0_offset对齐时才需要
struct arena_bin_info_s {
    /* Size of regions in a run for this bin's size class. */
    size_t        reg_size;在一个run中为这个bin的大小类规定的region大小(参加SIZE_CLASS表)

    /* Redzone size. */
    size_t        redzone_size; 调试观察,是0

    /* Interval between regions (reg_size + (redzone_size << 1)). */
    size_t        reg_interval;这里等于reg_size

    /* Total size of a run for this bin's size class. */
    size_t        run_size;为这个bin的大小类规定的run size有多大

    /* Total number of regions in a run for this bin's size class. */
    uint32_t    nregs; 在为这个bin的大小类规定的run中有几个region

    /*
     * Metadata used to manipulate bitmaps for runs associated with this
     * bin.
     */用来操作(run和这个run关联的bins的)位图的元数据
    bitmap_info_t    bitmap_info;

    /* Offset of first region in a run for this bin's size class. */
    uint32_t    reg0_offset;为这个bin的大小类规定的run中,第一个region的偏移,调试看都是0
};

struct bitmap_level_s {
    /* Offset of this level's groups within the array of groups. */
    size_t group_offset;
};

struct bitmap_info_s {
    /* Logical number of bits in bitmap (stored at bottom level). */
    size_t nbits; 在位图中bits的逻辑数

    /* Number of levels necessary for nbits. */
    unsigned nlevels;对于nbits必须的levels数量

    /*
     * Only the first (nlevels+1) elements are used, and levels are ordered
     * bottom to top (e.g. the bottom level is stored in levels[0]).
     */只有第一个元素(nlevels+1)被使用4,leves排序从低到高,
    bitmap_level_t levels[BITMAP_MAX_LEVELS+1]; 3 + 1 = 4
        #define    LG_RUN_MAXREGS        (LG_PAGE - LG_TINY_MIN) 12 - 3
        #define    LG_BITMAP_MAXBITS    LG_RUN_MAXREGS 9
        #define    BITMAP_MAXBITS        (ZU(1) << LG_BITMAP_MAXBITS) 512
        /* Maximum number of levels possible. */ 最大可能的层数
        #define    BITMAP_MAX_LEVELS                        \
            (LG_BITMAP_MAXBITS / LG_SIZEOF_BITMAP)                \ (9/3+0)=3
            + !!(LG_BITMAP_MAXBITS % LG_SIZEOF_BITMAP) 有余数加1,没余数加0
            
            64位一个long,定为1个组,可以表示64个region,实际情况是最多512个region,
            至少需要8个long,这一层是最低层,也就是最低层有8个group
            
            这8个group需要管理,来判断某个group是不是满了,8个位就够了,1个long更绰绰有余
            所以就有了第二层1个long
            
            (这里设想一下多层的情况,假设需要表示64*128 = 8192个region,需要128个long,
            也就是底层128个group,第二层要管理128个group,需要2个long,即第二层有2个group
            (中间group,一位表示底层的一个group是否满),根据下面的bitmap_info_init函数,
            只要group_count > 1就循环设置上一层,于是有了第三层,第三层1个long就可以管理2个group)
            下面的宏充分说明了计算方式
                #elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 2 表示的个数小于等于4096
                #  define BITMAP_GROUPS_MAX    BITMAP_GROUPS_2_LEVEL(BITMAP_MAXBITS)
                #elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 3 表示的个数 (4096, 256 * 1024)
                #  define BITMAP_GROUPS_MAX    BITMAP_GROUPS_3_LEVEL(BITMAP_MAXBITS)
                
            那么怎么计算层数呢,跟要表示的数的个数有关
            小于等于64(6),1层
            小于等于4096(12),2层
            小于等于256*1024(18),3层
            直观看就是6/6, 12/6, 18/6,刚好就是上面的对数相除
};


初始化,再研究bin_info_init
static void
bin_info_init(void)
{
    arena_bin_info_t *bin_info;

#define    BIN_INFO_INIT_bin_yes(index, size)                \
    bin_info = &arena_bin_info[index];                \
    bin_info->reg_size = size;                    \ 根据表计算
    bin_info_run_size_calc(bin_info);                \
    bitmap_info_init(&bin_info->bitmap_info, bin_info->nregs);
    
#define    BIN_INFO_INIT_bin_no(index, size)

#define    SC(index, lg_grp, lg_delta, ndelta, bin, lg_delta_lookup)    \
    BIN_INFO_INIT_bin_##bin(index, (ZU(1)<<lg_grp) + (ZU(ndelta)<<lg_delta))
    函数内定义了局部的SC,定义内容也是一个宏,BIN_INFO_INIT_bin_yes或者BIN_INFO_INIT_bin_no
    SIZE_CLASSES
    这个单词就是宏展开的地方,如果bin的那一项是yes的话,就调用BIN_INFO_INIT_bin_yes展开
    index = binind, size = 2^lg_grp + 2^lg_delta, 是一个组的某一项代表的大小区间的最大值
#undef BIN_INFO_INIT_bin_yes
#undef BIN_INFO_INIT_bin_no
#undef SC
}

分析bin_info_run_size_calc
/* 计算满足以下约束的bin_info->run_size
 * Calculate bin_info->run_size such that it meets the following constraints:
 *
 *   *) bin_info->run_size <= arena_maxrun 小于等于je_arena_maxrun = 0x1f3000,
            这个大小是刚分配一个chunk(2M),并放完chunk的meta信息后剩余的大小
 *   *) bin_info->nregs <= RUN_MAXREGS
            #define    RUN_MAXREGS        (1U << LG_RUN_MAXREGS) 512
 *
 * bin_info->nregs and bin_info->reg0_offset are also calculated here, since
 * these settings are all interdependent. 这些设置都是内部互相依赖的
 */
static void
bin_info_run_size_calc(arena_bin_info_t *bin_info)
{
    size_t pad_size;
    size_t try_run_size, perfect_run_size, actual_run_size;
    uint32_t try_nregs, perfect_nregs, actual_nregs;

    if (config_fill && unlikely(opt_redzone)) {
        ...opt_redzone = false,这个分支没有走到
    } else {
        bin_info->redzone_size = 0;
        pad_size = 0;
    }
    
    bin_info->reg_interval = bin_info->reg_size + (bin_info->redzone_size << 1);
        reg_interval == reg_size

    /*
     * Compute run size under ideal conditions (no redzones, no limit on run
     * size).在理想情况下计算run size,不考虑redzones,run_size的大小
     */
    try_run_size = PAGE;
    try_nregs = try_run_size / bin_info->reg_size;
    do {
        perfect_run_size = try_run_size;
        perfect_nregs = try_nregs;

        try_run_size += PAGE;
        try_nregs = try_run_size / bin_info->reg_size;
    } while (perfect_run_size != perfect_nregs * bin_info->reg_size);
    assert(perfect_nregs <= RUN_MAXREGS);
    计算的准则是,run_size的大小能被reg_size整除

    actual_run_size = perfect_run_size;
    actual_nregs = (actual_run_size - pad_size) / bin_info->reg_interval; pad_size = 0

    /*
     * Redzones can require enough padding that not even a single region can
     * fit within the number of pages that would normally be dedicated to a
     * run for this size class.  Increase the run size until at least one
     * region fits.
     */
    while (actual_nregs == 0) {
        没走到
    }

    /*
     * Make sure that the run will fit within an arena chunk.
     */确保这个run满足在一个chunk内
    while (actual_run_size > arena_maxrun) {
        。。。也没走到
    }
    assert(actual_nregs > 0);
    assert(actual_run_size == s2u(actual_run_size));
        reg_size符合s2u,actual_run_size又能被reg_size整除,所以也满足s2u

    /* Copy final settings. */ 拷贝最终的设置
    bin_info->run_size = actual_run_size;
    bin_info->nregs = actual_nregs;
    bin_info->reg0_offset = actual_run_size - (actual_nregs * bin_info->reg_interval) - pad_size + bin_info->redzone_size;

    if (actual_run_size > small_maxrun) 如果某个run_size > je_small_maxrun,覆盖small_maxrun的值
        small_maxrun = actual_run_size; small_maxrun存储的是最大的run_size,都运行完后是0x7000(7页)

    。。。
}

void
bitmap_info_init(bitmap_info_t *binfo, size_t nbits) nbits = nregs,有多少个regions的意思
{
    unsigned i;
    size_t group_count;

    assert(nbits > 0);
    assert(nbits <= (ZU(1) << LG_BITMAP_MAXBITS)); 512

    /*
     * Compute the number of groups necessary to store nbits bits, and
     * progressively work upward through the levels until reaching a level
     * that requires only one group.
     */ 计算存储nbits个位(比如512位)需要多少个group,通过levels逐步向上工作,
        直到达到仅需要一个group的水平
    binfo->levels[0].group_offset = 0;
    group_count = BITMAP_BITS2GROUPS(nbits); 8
        /* Number of bits per group. */ 每组位的数量
        #define    LG_BITMAP_GROUP_NBITS        (LG_SIZEOF_BITMAP + 3) 3 + 3 = 6,一组能存储2^6位(64位)
        #define    BITMAP_GROUP_NBITS        (ZU(1) << LG_BITMAP_GROUP_NBITS) 64位
        #define    BITMAP_GROUP_NBITS_MASK        (BITMAP_GROUP_NBITS-1)

        /* Number of groups required to store a given number of bits. */
        #define    BITMAP_BITS2GROUPS(nbits)                    \ 存储这么多位需要多少组
            ((nbits + BITMAP_GROUP_NBITS_MASK) >> LG_BITMAP_GROUP_NBITS)
            如果要存储的位数不能被64整除,哪怕余数是1,group数量也增1
            
    for (i = 1; group_count > 1; i++) { group_count > 1循环就继续, group_count初始值是8
        assert(i < BITMAP_MAX_LEVELS); 意味着循环最多不超过2次(i=1,i=2)
        binfo->levels[i].group_offset = binfo->levels[i-1].group_offset + group_count;
            第一次,levels1.group_offset = 8
        group_count = BITMAP_BITS2GROUPS(group_count);
            第一次group_count = 1(8/64=0余8)
    }
    binfo->levels[i].group_offset = binfo->levels[i-1].group_offset + group_count;
        levels2.group_offset = 8 + 1 = 9
    assert(binfo->levels[i].group_offset <= BITMAP_GROUPS_MAX); 9
        #elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 2
            9 <= 12
        #  define BITMAP_GROUPS_MAX    BITMAP_GROUPS_2_LEVEL(BITMAP_MAXBITS)
            BITMAP_MAXBITS 1 << 9 = 512
            BITMAP_GROUPS_1_LEVEL(nbits) + BITMAP_GROUPS_L1(nbits)
                BITMAP_GROUPS_L0(nbits) + BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(nbits))
                    BITMAP_BITS2GROUPS(nbits) + BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(nbits))
                        ((nbits + BITMAP_GROUP_NBITS_MASK) >> LG_BITMAP_GROUP_NBITS)
                        (512 + 63) >> 6 = 8  (8 + 63) >> 6 = 1
        8 + 1 = 9 这个运算和上面的循环是一样的
    binfo->nlevels = i; 2,2层
    binfo->nbits = nbits; 512
}
以上这些信息都是固定不变的,所有area共享

arena_bin_nonfull_run_get会调用je_bitmap_init
bitmap_init(run->bitmap, &bin_info->bitmap_info);
void
bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo)
{ bitmap需要设置,binfo是只读的
    size_t extra;
    unsigned i;

    /*
     * Bits are actually inverted with regard to the external bitmap
     * interface, so the bitmap starts out with all 1 bits, except for
     * trailing unused bits (if any).  Note that each group uses bit 0 to
     * correspond to the first logical bit in the group, so extra bits
     * are the most significant bits of the last group.
     */
    memset(bitmap, 0xffU, binfo->levels[binfo->nlevels].group_offset << LG_SIZEOF_BITMAP);
        bitmap是long类型的数组(BITMAP_GROUPS_MAX),levels2.group_offset = 9,
        9个long,9*8=72字节,每个字节都设置成-1
    extra = (BITMAP_GROUP_NBITS - (binfo->nbits & BITMAP_GROUP_NBITS_MASK)) & BITMAP_GROUP_NBITS_MASK;
        如果binfo->nbits(512)不是按照64对齐的,extra就是(64 - 余数)
    if (extra != 0)
        bitmap[binfo->levels[1].group_offset - 1] >>= extra;
        binind = 11,reg_size = 128
        binfo = {nbits = 32, nlevels = 1, levels = {{group_offset = 0}, {group_offset = 1},
            {group_offset = 0}, {group_offset = 0}}}
        bitmap[0]右移32位
    for (i = 1; i < binfo->nlevels; i++) {
        size_t group_count = binfo->levels[i].group_offset - binfo->levels[i-1].group_offset;
            8
        extra = (BITMAP_GROUP_NBITS - (group_count & BITMAP_GROUP_NBITS_MASK)) & BITMAP_GROUP_NBITS_MASK;
        if (extra != 0) 64 - 8
            bitmap[binfo->levels[i+1].group_offset - 1] >>= extra;
                bitmap[levels2.group_offset - 1] = bitmap[8] >>= 56
    }
}
初始化完后bitmap数组(动态的)的内存布局
Low Address---->High Address(小端机器)
|ff...ff|ff...ff|ff...ff|ff...ff|ff...ff|ff...ff|ff...ff|ff...ff|ff00...00|

分配内存通过regind = bitmap_sfu(run->bitmap, &bin_info->bitmap_info);实现
得到regind,就可以通过
ret = (void *)((uintptr_t)rpages + (uintptr_t)bin_info->reg0_offset + (uintptr_t)(bin_info->reg_interval * regind)
得到要分配的地址
bitmap_info[0] = {nbits = 512, nlevels = 2,
      levels = {{group_offset = 0}, {group_offset = 8}, {group_offset = 9}, {group_offset = 0}}}
      
JEMALLOC_INLINE size_t
bitmap_sfu(bitmap_t *bitmap, const bitmap_info_t *binfo)
{
    size_t bit;
    bitmap_t g;
    unsigned i;

    assert(!bitmap_full(bitmap, binfo));

    i = binfo->nlevels - 1; 1
    g = bitmap[binfo->levels[i].group_offset]; 8
        255,一个long是255,他的高位地址都被置0了
        选定最顶层的那个long
    bit = jemalloc_ffsl(g) - 1;
        __builtin_ffsl,返回右起第一个1的位置,从1开始, 所以要减1
        找到第一个空闲的group(最高层次的group)
    
    while (i > 0) { 如果i == 0,说明得到的bit是代表某个位被使用,而不是某个组满了,这时就找了这个bit
        i--;上来先降一个层次,到每个具体的group层
        g = bitmap[binfo->levels[i].group_offset + bit]; 0 + 0
            这一层的group在bitmap数组中的偏移 + 第一个空闲的group的偏移
            得到目标group在bitmap数组中的偏移,通过bitmap数组得到目标long
            bitmap[0] = -1
        bit = (bit << LG_BITMAP_GROUP_NBITS) + (jemalloc_ffsl(g) - 1);
            bit先表示第一个空闲的group的偏移,1个偏移是1个long
            后来表示第一个空闲的group的第一个空闲位,代表的是第几个region
            所以先用group乘以64,表示这个group表示的最小region,再加上group内部第一个1的偏移
            得到最小的空闲region
            0 + 0
    }

    bitmap_set(bitmap, binfo, bit);找到空闲的region id后,更改位图信息
    return (bit); 返回0,作为reg的id
}
bitmap_sfu总结为一句话,自上而下查找空闲位
bitmap_set总结为一句话,自下而上置位

JEMALLOC_INLINE bool
bitmap_full(bitmap_t *bitmap, const bitmap_info_t *binfo)
{
    unsigned rgoff = binfo->levels[binfo->nlevels].group_offset - 1; 9 - 1
    bitmap_t rg = bitmap[rgoff]; rgoff = 8
        bitmap[8]初始化时右移了56位,还剩8个1,如果这8个1都置0了,就满了
    /* The bitmap is full iff the root group is 0. */
    return (rg == 0);
}

一个位被选中后,意味着一块内存被选中,下面是更改位图信息
JEMALLOC_INLINE void
bitmap_set(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
{
    size_t goff;
    bitmap_t *gp;
    bitmap_t g;

    assert(bit < binfo->nbits); 0 < 512
    assert(!bitmap_get(bitmap, binfo, bit)); bit对应的那个位应该没有用过,是1
    goff = bit >> LG_BITMAP_GROUP_NBITS;
    gp = &bitmap[goff];
        这里属于一级位图,goff直接作为索引
    g = *gp;先找到bit对应的那个long
    assert(g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK))); bit对应的那个long的那个位是1
    g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);将bit对应的那个位设置成0,
        其他位呢,以前使用的位是0,0和0异或,还是0,以前没使用的是1,1和0异或还是1,所以其他位保持不变
    *gp = g;
    assert(bitmap_get(bitmap, binfo, bit));确保bit那个位设置成0了
    
    /* Propagate group state transitions up the tree. */ 向树的上方传播组的状态迁移
    if (g == 0) { 如果这个组满了,所有位都置成0了
        unsigned i;
        for (i = 1; i < binfo->nlevels; i++) {
            bit = goff;
            goff = bit >> LG_BITMAP_GROUP_NBITS; 上面这两句可以合并goff = goff >> LG_BITMAP_GROUP_NBITS;
                goff自己属于哪个long(bit原来属于goff这个long,goff属于哪个long呢,就属于现在的goff这个long)
            gp = &bitmap[binfo->levels[i].group_offset + goff];
                这是二级位图,二级以上的都加上了group_offset,8
            g = *gp;
            assert(g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK)));
            g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
            *gp = g; 二级位图的含义是某个位代表某个group,置0,说明这个group满了
            if (g != 0)
                break;
        }
    }
}

JEMALLOC_INLINE bool
bitmap_get(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
{
    size_t goff;
    bitmap_t g;

    assert(bit < binfo->nbits); 0 < 512
    goff = bit >> LG_BITMAP_GROUP_NBITS; bit这个数对应哪个long
        右移(LG_SIZEOF_BITMAP + 3)6位,512右移6为是8,511是7,这里是0
    g = bitmap[goff]; -1
    return (!(g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK))));这个long的哪一位对应这个bit
        bit对64取余数a,看g的第a位(从右从0开始)是1还是0,是1,返回0,是0,返回1
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值