14

Netty 源码解析 -- 内存对齐类 SizeClasses

 3 years ago
source link: https://xie.infoq.cn/article/237a97c55aec6731e295be467
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在学习Netty内存池之前,我们先了解一下Netty的内存对齐类SizeClasses,它为Netty内存池中的内存块提供大小对齐,索引计算等服务方法。

源码分析基于Netty 4.1.52

Netty内存池中每个内存块size都符合如下计算公式

size = 1 << log2Group + nDelta * (1 << log2Delta)

log2Group:内存块分组

nDelta:增量乘数

log2Delta:增量大小的log2值

SizeClasses初始化后,将计算chunkSize(内存池每次向操作系统申请内存块大小)范围内每个size的值,保存到sizeClasses字段中。

sizeClasses是一个表格(二维数组),共有7列,含义如下

index:内存块size的索引

log2Group:内存块分组,用于计算对应的size

log2Delata:增量大小的log2值,用于计算对应的size

nDelta:增量乘数,用于计算对应的size

isMultipageSize:表示size是否为page的倍数

isSubPage:表示是否为一个subPage类型

log2DeltaLookup:如果size存在位图中的,记录其log2Delta,未使用

sizeClasses负责计算sizeClasses表格

private int sizeClasses() {
int normalMaxSize = -1;

int index = 0;
int size = 0;
// #1
int log2Group = LOG2_QUANTUM;
int log2Delta = LOG2_QUANTUM;
int ndeltaLimit = 1 << LOG2_SIZE_CLASS_GROUP;

// #2
int nDelta = 0;
while (nDelta < ndeltaLimit) {
size = sizeClass(index++, log2Group, log2Delta, nDelta++);
}
log2Group += LOG2_SIZE_CLASS_GROUP;

// #3
while (size < chunkSize) {
nDelta = 1;

while (nDelta <= ndeltaLimit && size < chunkSize) {
size = sizeClass(index++, log2Group, log2Delta, nDelta++);
normalMaxSize = size;
}

log2Group++;
log2Delta++;
}

//chunkSize must be normalMaxSize
assert chunkSize == normalMaxSize;

//return number of size index
return index;
}

LOG2_QUANTUM=4

LOG2 SIZE CLASS_GROUP=2

#1 log2Group,log2Delta都是从LOG2_QUANTUM开始

ndeltaLimit为2^LOG2 SIZE CLASS_GROUP,即内存块size以4个为一组进行分组

#2 初始化第0组

nDelta从0开始

sizeClass方法计算每个size大小

注意:第0组后log2Group增加LOG2 SIZE CLASS_GROUP,而log2Delta不变

#3 初始化后面的size

nDelta从1开始

每组log2Group+1,log2Delta+1

log2Group=log2Delta+LOG2_SIZE_CLASS_GROUP 代入计算公式中,得到

size = 1 << (log2Delta+LOG2_SIZE_CLASS_GROUP) + nDelta * (1 << log2Delta)

size = (nDelta + 2 ^ LOG2_SIZE_CLASS_GROUP) * (1 << log2Delta)

可以看到,每个内存块size都是(1 << log2Delta)的倍数

从第二组开始,每组内这个倍数依次是5,6,7,8

每组内相邻行大小增量为(1 << log2Delta),相邻组之间(1 << log2Delta)翻倍。

Netty默认的配置一个page的大小是2^13,即为1KB,默认的一个chunk的大小为16777216,即2MB。sizeClasses表格内存如下:

q632YbQ.png!mobile

size并不是sizeClasses表格的列,这里为了直观而列出。

Netty内存池中管理了大小不同的内存块,对于这些不同大小的内存块,Netty划分为不同的等级Small,Normal,Huge

sizeClasses表格可以分为两部分

  1. isSubPage为1的size为Small内存块,其他为Normal内存块,而大于chunkSize的为huge内存块,不在表格中。

分配Small内存块,需要找到对应的index

通过size2SizeIdx方法计算index

比如需要分配一个360位的内存块,需要从sizeClasses表格找到第一个大于360的内存块size,即384,其index为13

  1. Normal内存块必须是page的倍数。

将isMultipageSize为1的行取出组成另一个表格

MvEfUjf.png!mobile

pageIdx并不是sizeClasses表格的列,它是这个新表格的索引。

PoolChunk中分配Normal内存块需求查询对应的pageIdx。

比如要分配一个50000位的内存块,需要从这个新表格找到第一个大于50000的内存块size,即57344,其pageIdx为6

通过pages2pageIdxCompute方法计算pageIdx。

下面看一下具体的计算方法

public int size2SizeIdx(int size) {
if (size == 0) {
return 0;
}
// #1
if (size > chunkSize) {
return nSizes;
}
// #2
if (directMemoryCacheAlignment > 0) {
size = alignSize(size);
}
// #3
if (size <= lookupMaxSize) {
//size-1 / MIN_TINY
return size2idxTab[size - 1 >> LOG2_QUANTUM];
}
// #4
int x = log2((size << 1) - 1);
// #5
int shift = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
? 0 : x - (LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM);

int group = shift << LOG2_SIZE_CLASS_GROUP;
// #6
int log2Delta = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
? LOG2_QUANTUM : x - LOG2_SIZE_CLASS_GROUP - 1;
// #7
int deltaInverseMask = -1 << log2Delta;
int mod = (size - 1 & deltaInverseMask) >> log2Delta &
(1 << LOG2_SIZE_CLASS_GROUP) - 1;

return group + mod;
}

#1 大于chunkSize,就是返回nSizes代表申请的是Huge内存块。

#2 不使用sizeClasses表格,将申请内存大小转换为directMemoryCacheAlignment的倍数,directMemoryCacheAlignment默认为0。

#3 SizeClasses将一部分较小的size与对应index记录在size2idxTab作为位图,这里直接查询size2idxTab,避免重复计算

size2idxTab中保存了(size-1)/(2^LOG2_QUANTUM) --> idx的对应关系。

#4 对申请内存大小进行log2的向上取整,就是每组最后一个内存块size。-1是为了避免申请内存大小刚好等于2的指数次幂时被翻倍。

log2Group = log2Delta + LOG2_SIZE_CLASS_GROUP nDelta=2^LOG2_SIZE_CLASS_GROUP 代入计算公式,可得

lastSize = 1 << (log2Group + 1)

x = log2Group + 1

#5 shift, 当前在第几组,从0开始(sizeClasses表格中0~3行为第0组,4~7行为第1组,以此类推,不是log2Group)

x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1 ,即 log2Group < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM ,满足该条件的是第0组的size,这时shift固定是0。

从sizeClasses方法可以看到,除了第0组,都满足 shift = log2Group - LOG2_QUANTUM - (LOG2_SIZE_CLASS_GROUP - 1)

shift << LOG2_SIZE_CLASS_GROUP 就是该组第一个内存块size的索引

#6 计算log2Delta

第0组固定是LOG2_QUANTUM

除了第0组,将 nDelta = 2^LOG2_SIZE_CLASS_GROUP 代入计算公式

lastSize = ( 2^LOG2_SIZE_CLASS_GROUP + 2^LOG2_SIZE_CLASS_GROUP ) * (1 << log2Delta)

lastSize = (1 << log2Delta) << LOG2_SIZE_CLASS_GROUP << 1

#7 前面已经定位到第几组了,下面要找到申请内存大小应分配在该组第几位

这里要找到比申请内存大的最小size。

申请内存大小可以理解为上一个size加上一个不大于(1 << log2Delta)的值,即

(nDelta - 1 + 2^LOG2_SIZE_CLASS_GROUP) * (1 << log2Delta) + n , 备注:0 < n <= (1 << log2Delta)

nDelta - 1就是mod

& deltaInverseMask ,将申请内存大小最后log2Delta个bit位设置为0,可以理解为减去n

>> log2Delta ,右移log2Delta个bit位,就是除以(1 << log2Delta),结果就是(nDelta - 1 + 2 ^ LOG2 SIZE CLASS_GROUP)

& (1 << LOG2_SIZE_CLASS_GROUP) - 1 , 取最后的LOG2 SIZE CLASS_GROUP个bit位的值,结果就是mod

size - 1 ,是为了申请内存等于内存块size时避免分配到下一个内存块size中,即n == (1 << log2Delta)的场景。

疑问:既然右移log2Delta个bit位,那为什么前面要将log2Delta个bit位设置为0?

第0组由于log2Group等于log2Delta,代入计算公式如下

1 << log2Delta + (nDelta - 1) * (1 << log2Delta) + n , 备注:0 < n <= (1 << log2Delta)

nDelta * (1 << log2Delta) + n

所以第0组nDelta从0开始,mod = nDelta

pages2pageIdxCompute方法计算pageIdx逻辑与size2SizeIdx方法类似,只是将LOG2_QUANTUM变量换成了pageShifts,这里不再重复。

SizeClasses是给PoolArena(内存池),PoolChunk(内存块)提供服务的,建议大家结合后面分析PoolArena,PoolChunk的文章一起理解。

如果大家对SizeClasses具体算法不感兴趣,只有理解SizeClasses类中利用sizeClasses表格,为PoolArena,PoolChunk提供计算index,pageIdx索引的方法,也可以帮助大家理解后面解析PoolArena,PoolChunk的文章。

下面贴出sizeClasses完整表格(可复制到Excle,以|分列)

| index | log2Group | log2Delta | nDelta | isMultiPageSize | isSubPage | log2DeltaLookup | size |
| 0 | 4 | 4 | 0 | 0 | 1 | 4 | 16 -- 2B |
| 1 | 4 | 4 | 1 | 0 | 1 | 4 | 32 -- 4B |
| 2 | 4 | 4 | 2 | 0 | 1 | 4 | 48 -- 6B |
| 3 | 4 | 4 | 3 | 0 | 1 | 4 | 64 -- 8B |
| 4 | 6 | 4 | 1 | 0 | 1 | 4 | 80 -- 10B |
| 5 | 6 | 4 | 2 | 0 | 1 | 4 | 96 -- 12B |
| 6 | 6 | 4 | 3 | 0 | 1 | 4 | 112 -- 14B |
| 7 | 6 | 4 | 4 | 0 | 1 | 4 | 128 -- 16B |
| 8 | 7 | 5 | 1 | 0 | 1 | 5 | 160 -- 20B |
| 9 | 7 | 5 | 2 | 0 | 1 | 5 | 192 -- 24B |
| 10 | 7 | 5 | 3 | 0 | 1 | 5 | 224 -- 28B |
| 11 | 7 | 5 | 4 | 0 | 1 | 5 | 256 -- 32B |
| 12 | 8 | 6 | 1 | 0 | 1 | 6 | 320 -- 40B |
| 13 | 8 | 6 | 2 | 0 | 1 | 6 | 384 -- 48B |
| 14 | 8 | 6 | 3 | 0 | 1 | 6 | 448 -- 56B |
| 15 | 8 | 6 | 4 | 0 | 1 | 6 | 512 -- 64B |
| 16 | 9 | 7 | 1 | 0 | 1 | 7 | 640 -- 80B |
| 17 | 9 | 7 | 2 | 0 | 1 | 7 | 768 -- 96B |
| 18 | 9 | 7 | 3 | 0 | 1 | 7 | 896 -- 112B |
| 19 | 9 | 7 | 4 | 0 | 1 | 7 | 1024 -- 128B |
| 20 | 10 | 8 | 1 | 0 | 1 | 8 | 1280 -- 160B |
| 21 | 10 | 8 | 2 | 0 | 1 | 8 | 1536 -- 192B |
| 22 | 10 | 8 | 3 | 0 | 1 | 8 | 1792 -- 224B |
| 23 | 10 | 8 | 4 | 0 | 1 | 8 | 2048 -- 256B |
| 24 | 11 | 9 | 1 | 0 | 1 | 9 | 2560 -- 320B |
| 25 | 11 | 9 | 2 | 0 | 1 | 9 | 3072 -- 384B |
| 26 | 11 | 9 | 3 | 0 | 1 | 9 | 3584 -- 448B |
| 27 | 11 | 9 | 4 | 0 | 1 | 9 | 4096 -- 512B |
| 28 | 12 | 10 | 1 | 0 | 1 | 0 | 5120 -- 640B |
| 29 | 12 | 10 | 2 | 0 | 1 | 0 | 6144 -- 768B |
| 30 | 12 | 10 | 3 | 0 | 1 | 0 | 7168 -- 896B |
| 31 | 12 | 10 | 4 | 1 | 1 | 0 | 8192 -- 1.0KB |
| 32 | 13 | 11 | 1 | 0 | 1 | 0 | 10240 -- 1.25KB |
| 33 | 13 | 11 | 2 | 0 | 1 | 0 | 12288 -- 1.5KB |
| 34 | 13 | 11 | 3 | 0 | 1 | 0 | 14336 -- 1.75KB |
| 35 | 13 | 11 | 4 | 1 | 1 | 0 | 16384 -- 2.0KB |
| 36 | 14 | 12 | 1 | 0 | 1 | 0 | 20480 -- 2.5KB |
| 37 | 14 | 12 | 2 | 1 | 1 | 0 | 24576 -- 3.0KB |
| 38 | 14 | 12 | 3 | 0 | 1 | 0 | 28672 -- 3.5KB |
| 39 | 14 | 12 | 4 | 1 | 0 | 0 | 32768 -- 4.0KB |
| 40 | 15 | 13 | 1 | 1 | 0 | 0 | 40960 -- 5.0KB |
| 41 | 15 | 13 | 2 | 1 | 0 | 0 | 49152 -- 6.0KB |
| 42 | 15 | 13 | 3 | 1 | 0 | 0 | 57344 -- 7.0KB |
| 43 | 15 | 13 | 4 | 1 | 0 | 0 | 65536 -- 8.0KB |
| 44 | 16 | 14 | 1 | 1 | 0 | 0 | 81920 -- 10.0KB |
| 45 | 16 | 14 | 2 | 1 | 0 | 0 | 98304 -- 12.0KB |
| 46 | 16 | 14 | 3 | 1 | 0 | 0 | 114688 -- 14.0KB |
| 47 | 16 | 14 | 4 | 1 | 0 | 0 | 131072 -- 16.0KB |
| 48 | 17 | 15 | 1 | 1 | 0 | 0 | 163840 -- 20.0KB |
| 49 | 17 | 15 | 2 | 1 | 0 | 0 | 196608 -- 24.0KB |
| 50 | 17 | 15 | 3 | 1 | 0 | 0 | 229376 -- 28.0KB |
| 51 | 17 | 15 | 4 | 1 | 0 | 0 | 262144 -- 32.0KB |
| 52 | 18 | 16 | 1 | 1 | 0 | 0 | 327680 -- 40.0KB |
| 53 | 18 | 16 | 2 | 1 | 0 | 0 | 393216 -- 48.0KB |
| 54 | 18 | 16 | 3 | 1 | 0 | 0 | 458752 -- 56.0KB |
| 55 | 18 | 16 | 4 | 1 | 0 | 0 | 524288 -- 64.0KB |
| 56 | 19 | 17 | 1 | 1 | 0 | 0 | 655360 -- 80.0KB |
| 57 | 19 | 17 | 2 | 1 | 0 | 0 | 786432 -- 96.0KB |
| 58 | 19 | 17 | 3 | 1 | 0 | 0 | 917504 -- 112.0KB |
| 59 | 19 | 17 | 4 | 1 | 0 | 0 | 1048576 -- 128.0KB |
| 60 | 20 | 18 | 1 | 1 | 0 | 0 | 1310720 -- 160.0KB |
| 61 | 20 | 18 | 2 | 1 | 0 | 0 | 1572864 -- 192.0KB |
| 62 | 20 | 18 | 3 | 1 | 0 | 0 | 1835008 -- 224.0KB |
| 63 | 20 | 18 | 4 | 1 | 0 | 0 | 2097152 -- 256.0KB |
| 64 | 21 | 19 | 1 | 1 | 0 | 0 | 2621440 -- 320.0KB |
| 65 | 21 | 19 | 2 | 1 | 0 | 0 | 3145728 -- 384.0KB |
| 66 | 21 | 19 | 3 | 1 | 0 | 0 | 3670016 -- 448.0KB |
| 67 | 21 | 19 | 4 | 1 | 0 | 0 | 4194304 -- 512.0KB |
| 68 | 22 | 20 | 1 | 1 | 0 | 0 | 5242880 -- 640.0KB |
| 69 | 22 | 20 | 2 | 1 | 0 | 0 | 6291456 -- 768.0KB |
| 70 | 22 | 20 | 3 | 1 | 0 | 0 | 7340032 -- 896.0KB |
| 71 | 22 | 20 | 4 | 1 | 0 | 0 | 8388608 -- 1.0MB |
| 72 | 23 | 21 | 1 | 1 | 0 | 0 | 10485760 -- 1.25MB |
| 73 | 23 | 21 | 2 | 1 | 0 | 0 | 12582912 -- 1.5MB |
| 74 | 23 | 21 | 3 | 1 | 0 | 0 | 14680064 -- 1.75MB |
| 75 | 23 | 21 | 4 | 1 | 0 | 0 | 16777216 -- 2.0MB |

如果您觉得本文不错,欢迎关注我的微信公众号,您的关注是我坚持的动力!

Q3QNfuQ.jpg!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK