• Netty内存池源码三 (SizeClasses终结)


    Netty-内存池源码三 (SizeClasses终结)

    内存池核心类如下:

    • PooledByteBufAllocator
    • PooledUnsafeDirectByteBuf
    • PooledUnsafeDirectByteBuf
    • PoolThreadCache
    • MemoryRegionCache
    • PoolArena
    • SizeClasses 本期介绍
    • PoolChunk
    • LongPriorityQueue
    • LongLongHashMap
    • PoolSubpage

    上期 大致了解了 【SizeClasses】 内主要的工作就是 生成 4 张表,供后面的内存分配时使用。 可以更加详细的划分内存规格,减少内存碎片

    本期就是对 SizeClasses 类的源码进行一个透彻的分析, 主要要搞清楚 jemalloc4 这样修改有什么好处。

    成员变量

    首先来看下关键的成员变量

        // 因为sizeClass规格中最小的就是 16B, 因此这里就是定义最小的规格  log2(16) = 4
    	// 最小规格的移位
    	static final int LOG2_QUANTUM = 4;
    
    	// 因为sizeClasses总表中 是按照每4行为1组,所以 log2(4) = 2
     	// 定义sizeClasses中 4行 为一组,移位为2
        private static final int LOG2_SIZE_CLASS_GROUP = 2;
    
    	// 因为size2idxTab 详细划分查找表中 最大限制size大小为4096,所以这里 log2(4096) = 12
    	// 定义最大详细划分查找表的规格限制为4096,移位为12
        private static final int LOG2_MAX_LOOKUP_SIZE = 12;
    	
    	// 以下7个常量,对应着 sizeClasses总表的 7个列字段
    	
    	// index
        private static final int INDEX_IDX = 0;
    	// log2Group
        private static final int LOG2GROUP_IDX = 1;
    	// log2Delta
        private static final int LOG2DELTA_IDX = 2;
    	// nDelta
        private static final int NDELTA_IDX = 3;
    	// pageSize => isMultiPageSize
        private static final int PAGESIZE_IDX = 4;
    	// subpPage
        private static final int SUBPAGE_IDX = 5;
    	// log2DeltaLookup
        private static final int LOG2_DELTA_LOOKUP_IDX = 6;
    	
    	// 定义常量 yes no
        private static final byte no = 0, yes = 1;
    
    	// 一页的大小 默认8KB
        protected final int pageSize;
    	
    	// 一页的移位 默认13
        protected final int pageShifts;
    
    	// 一chunk的大小 默认16mb
        protected final int chunkSize;
    
    	// 对齐填充 0
        protected final int directMemoryCacheAlignment;
    	
    	// 总规格数量 默认 76
        final int nSizes;
    	
    	// subPage的数量 默认39
        int nSubpages;
    
    	// 页倍数的规格数量 默认40
        int nPSizes;
    
    	// 记录最大Subpage的 index 默认是38
        int smallMaxSizeIdx;
    	
    	// 4K(4096) 
        private int lookupMaxSize;
    	
    	// sizeClasses 总表
        private final short[][] sizeClasses;
    	
    	// 规格大小为页的倍数 的表  记录 pageIndex->size
        private final int[] pageIdx2sizeTab;
    
    	// 查询表 记录所有规格的 index=>size 的表
        private final int[] sizeIdx2sizeTab;
    
        // lookup table used for size <= lookupMaxclass
        // spacing is 1 << LOG2_QUANTUM, so the size of array is lookupMaxclass >> LOG2_QUANTUM
    	// 查询表  对容量<= 4K的规格,按照16B 为增量 做个详细的划分。 idx=>index  有256个(4096>>4)
        private final int[] size2idxTab;
    

    由上述常量和变量可看出来,大量的使用了 log2() 来规划这些值。 刚开始阅读的时候,会不习惯,为什么要使用log2() 来表示这些值呢? 用原值不行吗?

    我个人觉得大致的原因: 使用log2()来计算,可以化整 ,且保证最终移位回来还是 power of two

    例如:某个用户设置一页的大小为8193,那么log2(8193) = 13, 最终移位13后 还是 8192 (1<<13) 。

    构造方法

        protected SizeClasses(int pageSize, int pageShifts, int chunkSize, int directMemoryCacheAlignment) {
    
            // 每一页的大小(默认为 8192 8KB)
            this.pageSize = pageSize;
    
            // 1左移多少位  为8192  (默认为 13)
            this.pageShifts = pageShifts;
    
            // 这个chunk的大小 (默认为16777216,即16mb)
            this.chunkSize = chunkSize;
    
            // 主要是对于Huge这种直接分配的 类型的 数据 将它 对齐为 directMemoryCacheAlignment的倍数(可以忽略该变量,对后面的逻辑没有影响)
            this.directMemoryCacheAlignment = directMemoryCacheAlignment;
    
            // 16mb => 16,777,216 =>  0000 0001 0000 0000 0000 0000 0000 0000
            // log2(chunkSize) = 31 - 7 = 24
            // 24 + 1 - 4 = 21 => group
            int group = log2(chunkSize) + 1 - LOG2_QUANTUM;
    
            //generate size classes
            //[index, log2Group, log2Delta, nDelta, isMultiPageSize, isSubPage, log2DeltaLookup]
            // 21 << 2  => 21*4=84
            // sizeClasses = {84}{7}
            sizeClasses = new short[group << LOG2_SIZE_CLASS_GROUP][7];
            
            // 生成sizeClasses表数据
            nSizes = sizeClasses();
    
            //generate lookup table
            // 下面都是生成 查询表的数据了
    
            //sizeIdx2sizeTab  size 关联 id的表
            sizeIdx2sizeTab = new int[nSizes];
    
            //pageIdx2sizeTab  size是页的整数倍的表 (维护关系 id和size)
            pageIdx2sizeTab = new int[nPSizes];
    
            // 填充sizeIdx2sizeTab 和 pageIdx2sizeTab 这两张表的数据
            idx2SizeTab(sizeIdx2sizeTab, pageIdx2sizeTab);
    
            //  4096 >> 4 = 256
            size2idxTab = new int[lookupMaxSize >> LOG2_QUANTUM];
            size2idxTab(size2idxTab);
        }
    

    sizeClasses

        // 生成 sizeClasses二维数组表
        private int sizeClasses() {
            int normalMaxSize = -1;
    
            int index = 0;
            int size = 0;
    
            // log2Group 和 log2Delta 初始值 4
            int log2Group = LOG2_QUANTUM;
            int log2Delta = LOG2_QUANTUM;
    
            // ndeltaLimit = 1<<2 = 4  最大增量为4  也就是限制 1个group中有4个size
            int ndeltaLimit = 1 << LOG2_SIZE_CLASS_GROUP;
    
            //First small group, nDelta start at 0.   第一个 small组 nDetla从0开始
            //first size class is 1 << LOG2_QUANTUM   第一个 size 是 1<<4 = 16
    
            int nDelta = 0;
    
            // 生成 第一组的值,放入sizeClasses二维数组中
            // nDelta 从0开始  不超过4  也就是一组有4个size
            while (nDelta < ndeltaLimit) {
    
                // 给sizeClasses二维数组赋值
                // 第一组 index = 0  log2Group=4  log2Delta=4 nDelta=0  根据这几个值计算并返回size值
                size = sizeClass(index++, log2Group, log2Delta, nDelta++);
            }
            
            // 生成 第二组及后面组的值  
            // log2Group = 4 +2 = 6  log2Group开始为6  
            log2Group += LOG2_SIZE_CLASS_GROUP;
    
            //All remaining groups, nDelta start at 1.
            //剩下每组的 nDelta从1开始
            while (size < chunkSize) {
                nDelta = 1;
                while (nDelta <= ndeltaLimit && size < chunkSize) {
                    size = sizeClass(index++, log2Group, log2Delta, nDelta++);
                    // 记录最大的size值
                    normalMaxSize = size;
                }
    
                log2Group++;
                log2Delta++;
            }
    
            // 断言下 最后生成的 size值是否符合 chunkSize
            //chunkSize must be normalMaxSize
            assert chunkSize == normalMaxSize;
    
            //return number of size index
            // 返回最大index  默认应该是 75
            return index;
        }
    

    sizeClass

        private int sizeClass(int index, int log2Group, int log2Delta, int nDelta) {
            short isMultiPageSize;
    
            // log2Delta >= 13 的 其size都是页(8KB)的整倍数
            if (log2Delta >= pageShifts) {
                isMultiPageSize = yes;
            } else {
                // pageSize = 8192  8KB
                int pageSize = 1 << pageShifts;
    
    			// 计算size的公式
                int size = (1 << log2Group) + (1 << log2Delta) * nDelta;
    
                // 判断是不是 页的整数倍
                isMultiPageSize = size == size / pageSize * pageSize? yes : no;
            }
    
    		// 计算 log2(nDelta)
            int log2Ndelta = nDelta == 0? 0 : log2(nDelta);
    
    
            //remove 表示        当 size不是 2的次方的时候 为 yes
            //                   当 size是  2的次方的时候  为no
            // 该值只是当 size >= MAX_LOOKUP_SIZE(4096) 时使用 
            byte remove = 1 << log2Ndelta < nDelta? yes : no;
    
    
            // 实际上就是求 log2(size)的  只不过这样更方便而已 当nDelta=4的时候满足
            int log2Size = log2Delta + log2Ndelta == log2Group? log2Group + 1 : log2Group;
    		
            if (log2Size == log2Group) {
                remove = yes;
            }
    
            // 16B 32B ... 1024B 1280B .. 8K ... 28K       13+2 = 15  32768=32KB
            // pageShifts = log2(8K) = 13
            // pageShifts + LOG2_SIZE_CLASS_GROUP = 13 + 2 = 15  为 log2(32K)
            // 得出结论: 若 size < 32K 则是 subPage
            short isSubpage = log2Size < pageShifts + LOG2_SIZE_CLASS_GROUP? yes : no;
    
            // log2DeltaLookup   当size <= 4096(4K)  值为log2Delta
            //                   当size > 4096 时     值为0
            int log2DeltaLookup = log2Size < LOG2_MAX_LOOKUP_SIZE ||
                                  log2Size == LOG2_MAX_LOOKUP_SIZE && remove == no
                    ? log2Delta : no;
    
            // 生成行
            short[] sz = {
                    (short) index, (short) log2Group, (short) log2Delta,
                    (short) nDelta, isMultiPageSize, isSubpage, (short) log2DeltaLookup
            };
    
            // 插入数组
            sizeClasses[index] = sz;
            int size = (1 << log2Group) + (nDelta << log2Delta);
    
            // 判断该行是否是 页的整数倍 若是则 nPSizes+1
            if (sz[PAGESIZE_IDX] == yes) {
                nPSizes++;
            }
    
            // 判断该行是否是 subPage  若是则 nSubpages + 1 ,smallMaxSizeIdx记录最大的subPage的index
            if (sz[SUBPAGE_IDX] == yes) {
                nSubpages++;
                smallMaxSizeIdx = index;
            }
    
            // lookupMaxSize 记录 <= 4096(4K)  最大的size值
            if (sz[LOG2_DELTA_LOOKUP_IDX] != no) {
                lookupMaxSize = size;  // 最大4K
            }
            return size;
        }
    

    int log2Ndelta = nDelta == 0? 0 : log2(nDelta);

    byte remove = 1 << log2Ndelta < nDelta? yes : no;

    int log2Size = log2Delta + log2Ndelta == log2Group? log2Group + 1 : log2Group;

    sizeClass函数中 其中上述三行代码 刚开始 我看的时候 一脸懵逼, 完全不知道是什么意思,仔细研究后发现并不难。

    1. 首先 通过上一期我们可知道 size = (1 << log2Group) + (1 << log2Delta) * nDelta 该求size公式可转换成 size = 2^(log2Delta) * (4 + nDelta)

    2. int log2Ndelta = nDelta == 0? 0 : log2(nDelta);

      这行代码实际上不难理解,就是为了计算出 log2(nDelta) 的值。

      我们知道 求log2() 实际上 是向下取整, 比如 log2(5) = 2 , log2(3) = 1 等等 5 ,3这些数不是 POWER OF TWO 时,其 log2值 都是向下取整的。

    3. byte remove = 1 << log2Ndelta < nDelta? yes : no;

      remove 值 判断 1<< log2Ndelta < nDelta 就是为了看此值是否是2的倍数。 而我们清楚 除了第一组之外,nDelta取值范围为 [1,4] ,那么该区间范围内,只有4符合 POWER OF TWO, 也就是每组的最后一个规格。

    4. int log2Size = log2Delta + log2Ndelta == log2Group? log2Group + 1 : log2Group;

      我们知道 nDelta 第一组的取值范围为 [0,3], 除了第一组其它组取值范围为[1,4],

      为了让 size 的结果是 POWER OF TWO (2的次方数), 那么 nDelta 只能取 0 或 4 。

      当nDelta = 0 时 也就是只有第一组的第一个规格满足,此时 size = 1<<log2Group

      当nDelta = 4 时,也就是除了第一组外 其他组的最后一个规格满足,此时size = 1<<(log2Group + 1)

    idx2SizeTab

    该方法主要就是用来生成 sizeIdx2sizeTab表,与pageIdx2sizeTab 表的。

    很简单,没什么可说的。 生成的对应表详细信息, 可以在上一期查看。

        private void idx2SizeTab(int[] sizeIdx2sizeTab, int[] pageIdx2sizeTab) {
            int pageIdx = 0;
    
            // 循环遍历总表
            for (int i = 0; i < nSizes; i++) {
                short[] sizeClass = sizeClasses[i];
                int log2Group = sizeClass[LOG2GROUP_IDX];
                int log2Delta = sizeClass[LOG2DELTA_IDX];
                int nDelta = sizeClass[NDELTA_IDX];
    
                // 计算该行 sizeClass 的 size值
                int size = (1 << log2Group) + (nDelta << log2Delta);
                // 填充sizeIdx2sizeTab表
                sizeIdx2sizeTab[i] = size;
    
                // 如果 isMultiPageSize 是 yes 也就是说 该sizeClass的size值是 页的整数倍的时候 加入pageIdx2sizeTab表中
                if (sizeClass[PAGESIZE_IDX] == yes) {
                    pageIdx2sizeTab[pageIdx++] = size;
                }
            }
        }
    

    size2idxTab

    生成的size2idxTab 表 可参考上一期。

        private void size2idxTab(int[] size2idxTab) {
            int idx = 0;
            int size = 0;
    		
            // 遍历每一个 小于 4096 的规格
            for (int i = 0; size <= lookupMaxSize; i++) {
               	
                // 获取 log2Delta
                int log2Delta = sizeClasses[i][LOG2DELTA_IDX];
    
                // 该代码可理解成   2^log2Delta/16   
                // 目的是 求出 该规格 可以按照16划分多少份
                int times = 1 << log2Delta - LOG2_QUANTUM;
    
                // size <= 4096
               // 生成详细的 以16为增量的规格值
                while (size <= lookupMaxSize && times-- > 0) {
                    size2idxTab[idx++] = i;
                    size = idx + 1 << LOG2_QUANTUM;
                }
            }
        }
    

    size2SizeIdx

    该方法 是为了求出接近 用户申请内存大小 的内存规格下标。

    当申请内存 <= 4096 时,直接从 size2idxTab 表中获取

    当申请内存 > 4096 时,需要计算 group(申请内存所属组的第一个规格的index)+mod(该申请内存在所属组的偏移量)

    例如: 用户申请大小为14 ,则返回数组索引值为 0。

        public int size2SizeIdx(int size) {
            if (size == 0) {  // 当请求申请的size为0时, 则返回0
                return 0;
            }
            if (size > chunkSize) { //当请求申请的size大于chunkSize时, 返回最大索引 也就是chunkSize的index
                return nSizes;
            }
            
    		// 当前偏移量大于0时,默认该条件不会成立
            if (directMemoryCacheAlignment > 0) {  
                size = alignSize(size);
            }
    
            // 当size <= 4K 时  从size2idxTab 细粒度表中 直接获取index
            if (size <= lookupMaxSize) {   
                return size2idxTab[size - 1 >> LOG2_QUANTUM];
            }
    
            // 来到这 就是 size > 4K 的时候
            
            
    		// 该表达式是为了将申请内存向上取整(该size的移位),得出该size所属组的移位。
            // -1 是为了避免申请内存大小刚好是 POWER OF TWO(每组的最后一个规格),这样可以让每组的最后一个规格移位与同组其它规格移位相同。 
            // 最终 1<< 移位 = 当前组最后一个规格内存块的size
            
            // 例子1: size = 16KB    2B0100 0000 0000 0000
            //        (size << 1) - 1 = 2B0100 0000 0000 00000 - 1 
            //                        = 2B0011 1111 1111 11111
            // 			log2((size<<1)-1) = 14
            
            // 例子2: size = 13KB    2B0011 0100 0000 0000
            //          (size << 1) - 1 = 2B0011 0100 0000 00000 -1
            //                          = 2B0011 0011 1111 11111
            //          log2((size<<1)-1) = 14
            int x = log2((size << 1) - 1);
    		
           	
            /** 
            * 1. 从上得出x是每组最后一个内存块的size的移位。
            *
            * 2. 已知 log2Group = log2Delta +  LOG2_SIZE_CLASS_GROUP
            *  	除了第一组外,每组的最后一个内存块的 nDelta = 4
            *  	将上面两个已知条件代入求size的公式可得:   
            *  	lastSize = 1 << (log2Group + 1)
            *  	两边同时 取log2 得:
            *  	x  = log2Group + 1   
      		*/
    		 
             
            // x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1 
            // 将 x = log2Group + 1 代入后得:
         	// log2Group < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM 
            // log2Group < 6  时  shift = 0   (第一组)
            // log2Group >= 6 时  shift = log2Group - 5  (第二组及以后)
            // 最终shift 可以通过后面得表达式计算出 该组第一个内存块得index索引  
            int shift = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
                    ? 0 : x - (LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM);
    
            //  该表达式就是计算 该组得第一个内存块的 index
            int group = shift << LOG2_SIZE_CLASS_GROUP;
    		
            
            // 下面时计算 mod值 ,最终确定要分配得内存块是属于该组得第几个
            // 计算log2Delta 
            // 当第0组时   log2Delta = 4
            // 第一组及以后 log2Delta = log2Group - 2
            int log2Delta = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
                    ? LOG2_QUANTUM : x - LOG2_SIZE_CLASS_GROUP - 1;
    
            //  计算掩码
            // size = (1<<log2Delta) * (4 + nDelta)
            // index = group + mod  
            // group 是该组得第一个内存块索引  
            // mod 是偏移位置    也即是 nDelta - 1
            // size=(LOG2_SIZE_CLASS_GROUP + nDelta -1 + 1)* (1<<log2Delta)
           // 再进一步转换 将1提出来
    //size=(LOG2_SIZE_CLASS_GROUP + nDelta -1)*(1<<log2Delta)+(1<<log2Delta)
            
     //LOG2_SIZE_CLASS_GROUP + nDelta-1=(size-(1<<log2Delta))/(1<<log2Delta)
                                
            int deltaInverseMask = -1 << log2Delta;
    
            // size - 1 是为了申请内存等于内存块size时避免分配到下一个内存块size中
            // & deltaInverseMask 可以理解为 减去 1<<log2Delta 
            // >> log2Delta   可以理解成  除以 1<<log2Delta
            // 此时 就得到 LOG2_SIZE_CLASS_GROUP + nDelta-1
    //mod = LOG2_SIZE_CLASS_GROUP + nDelta-1 & (1<< LOG2_SIZE_CLASS_GROUP)-1
      //最终 mod = nDelta - 1
            int mod = (size - 1 & deltaInverseMask) >> log2Delta &
                      (1 << LOG2_SIZE_CLASS_GROUP) - 1;
          //28 + 3 = 31
            return group + mod;
        }
    

    该方法可能看上去比较复杂,晦涩难懂,但是仔细跟着代入公式算一遍,其实很好理解。

    page2pageIdxCompute

    该方法主要目的就是 根据申请得 页数,来计算该 页数 在 pageIdx2sizeTab 表中得 pageIdx

    该方法与 size2SizeIdx实际上计算思想相同,都是使用 group + mod

        private int pages2pageIdxCompute(int pages, boolean floor) {
            int pageSize = pages << pageShifts;
            if (pageSize > chunkSize) {
                return nPSizes;
            }
    
            int x = log2((pageSize << 1) - 1);
    
            int shift = x < LOG2_SIZE_CLASS_GROUP + pageShifts
                    ? 0 : x - (LOG2_SIZE_CLASS_GROUP + pageShifts);
    
            int group = shift << LOG2_SIZE_CLASS_GROUP;
    
            int log2Delta = x < LOG2_SIZE_CLASS_GROUP + pageShifts + 1?
                    pageShifts : x - LOG2_SIZE_CLASS_GROUP - 1;
    
            int deltaInverseMask = -1 << log2Delta;
            int mod = (pageSize - 1 & deltaInverseMask) >> log2Delta &
                      (1 << LOG2_SIZE_CLASS_GROUP) - 1;
    
            int pageIdx = group + mod;
    
            if (floor && pageIdx2sizeTab[pageIdx] > pages << pageShifts) {
                pageIdx--;
            }
    
            return pageIdx;
        }
    
    万般皆下品,唯有读书高!
  • 相关阅读:
    hadoop2.3.0cdh5.0.2 升级到cdh5.7.0
    strace
    ganglia3.7.2,web3.7.1安装
    hadoop balancer
    linux-小命令
    Ceph 架构以及原理分析
    Ceph 文件存储
    Ceph 对象存储
    Ceph 块存储
    Ceph 集群搭建
  • 原文地址:https://www.cnblogs.com/s686zhou/p/15714858.html
Copyright © 2020-2023  润新知