jemalloc源码解读(四)长度对齐算法

    在现代计算架构中,从内存中读取数据,基本上都是按2^N个字节来从主存加载CPU中。这个值,基本是cache line的大小。也就是说,如果一块内存是是在同一块cache line之内是最快的。目前来说,多数PC的cache line值是128个字节。 对于首地址也是一样的。在32位机器上,如果有4个字节的内存块,跨2个cache line,那么被加载到CPU的时候,需要2次内存缺失中断。

   好了,言归正传。对于任何一种小内存请求,都不会按实际大小分配,首先会按照一定规则进行对齐。这种对齐的规则比较复杂,一般会依照系统页大小,机器字大小,和系统特性来定制。通常来说,会在不同区间采用不同的步长。举个例子:

序号大小区间字节对齐
0[0--16]8
1(16 , 128]16
2(128 , 256]32
3(256 , 512]64

由于每个区间的步长不一样,又被划分成更多的区间。比如(256 , 320]之间长度请求,实际被分配应该是320个字节,而不是512。而1个字节的请求,总是被分配8个字节。


对于任意一个请求Size,为了快速得到他实际应该要被分配的实际长度。这个已经很难通过算法来完成了,因为这个区间的划分有一定不确定性,需要根据经验值来 调整。在jemalloc和tcmalloc他们都采用同样的算法,就是二次查表法。首先建立两个数组 size_to_index和 class_to_size ,size_to_index保存class_to_size的下标,而class_to_size保存对齐后的长度。从jemalloc的size_classes.h和arena.h中挖了一段代码出来,稍微修改后,如下:

typedef unsigned char uint8_t ;

#define	SIZE_CLASSES							\
    SIZE_CLASS(0,	8,	8)					\
    SIZE_CLASS(1,	8,	16)					\
    SIZE_CLASS(2,	16,	32)					\
    SIZE_CLASS(3,	16,	48)					\
    SIZE_CLASS(4,	16,	64)					\
    SIZE_CLASS(5,	16,	80)					\
    SIZE_CLASS(6,	16,	96)					\
    SIZE_CLASS(7,	16,	112)					\
    SIZE_CLASS(8,	16,	128)					\
    SIZE_CLASS(9,	32,	160)					\
    SIZE_CLASS(10,	32,	192)					\
    SIZE_CLASS(11,	32,	224)					\
    SIZE_CLASS(12,	32,	256)					\
    SIZE_CLASS(13,	64,	320)					\
    SIZE_CLASS(14,	64,	384)					\
    SIZE_CLASS(15,	64,	448)					\
    SIZE_CLASS(16,	64,	512)					\
    SIZE_CLASS(17,	128,	640)					\
    SIZE_CLASS(18,	128,	768)					\
    SIZE_CLASS(19,	128,	896)					\
    SIZE_CLASS(20,	128,	1024)					\
    SIZE_CLASS(21,	256,	1280)					\
    SIZE_CLASS(22,	256,	1536)					\
    SIZE_CLASS(23,	256,	1792)					\
    SIZE_CLASS(24,	256,	2048)					\
    SIZE_CLASS(25,	512,	2560)					\
    SIZE_CLASS(26,	512,	3072)					\
    SIZE_CLASS(27,	512,	3584)					\

#define	NBINS		28
#define	SMALL_MAXCLASS	3584

const uint8_t	small_size2bin[] = {
#define	S2B_8(i)	i,
#define	S2B_16(i)	S2B_8(i) S2B_8(i)
#define	S2B_32(i)	S2B_16(i) S2B_16(i)
#define	S2B_64(i)	S2B_32(i) S2B_32(i)
#define	S2B_128(i)	S2B_64(i) S2B_64(i)
#define	S2B_256(i)	S2B_128(i) S2B_128(i)
#define	S2B_512(i)	S2B_256(i) S2B_256(i)
#define	S2B_1024(i)	S2B_512(i) S2B_512(i)
#define	S2B_2048(i)	S2B_1024(i) S2B_1024(i)
#define	S2B_4096(i)	S2B_2048(i) S2B_2048(i)
#define	S2B_8192(i)	S2B_4096(i) S2B_4096(i)
#define	SIZE_CLASS(bin, delta, size)					\
	S2B_##delta(bin)
	SIZE_CLASSES
#undef S2B_8
#undef S2B_16
#undef S2B_32
#undef S2B_64
#undef S2B_128
#undef S2B_256
#undef S2B_512
#undef S2B_1024
#undef S2B_2048
#undef S2B_4096
#undef S2B_8192
#undef SIZE_CLASS
};

typedef struct __arena_bin_info_t{
    size_t reg_size ;
} arena_bin_info_t ;

arena_bin_info_t	arena_bin_info[NBINS];

void bin_info_init(void)
{
	arena_bin_info_t *bin_info;
	size_t prev_run_size = 4096;

#define	SIZE_CLASS(bin, delta, size)					\
	bin_info = &arena_bin_info[bin];				\
	bin_info->reg_size = size;					
	SIZE_CLASSES
#undef SIZE_CLASS
}


void test_size_class() 
{
    FILE * file = fopen("small_size2bin.txt" , "w+b") ;
    size_t size = sizeof(small_size2bin) ;
    fprintf(file , "sizeof[small_size2bin] = %u \n" , size) ;

    for(size_t idx = 0 ; idx < size ; ++idx)
    {
        fprintf(file , "idx[%u] , value[%hhu] \n" , idx , small_size2bin[idx]) ;    
    }

    fclose(file) ;
}

void test_reg_size() 
{
    memset(arena_bin_info , 0 , sizeof(arena_bin_info)) ;
    bin_info_init() ;

    FILE * file = fopen("arena_bin_info.txt" , "w+b") ;
    size_t size = sizeof(arena_bin_info) ;
    fprintf(file , "sizeof[arena_bin_info] = %u , count[%u]\n" , size , NBINS) ;

    for(size_t idx = 0 ; idx < NBINS ; ++idx)
    {
        fprintf(file , "idx[%u] , reg_size[%hhu] \n" , idx , arena_bin_info[idx].reg_size) ; 
    }

    fclose(file) ;
}

代码中的数组small_size2bin等价于 size_to_index,arena_bin_info数组等价于class_to_size。

依据这两个数据,对于任何一个长度,要得到对应的对齐长度,就很简单了,如下:


size_t align(size_t size)
{
    return arena_bin_info[size_to_index[size >> 3]] ;
}

这个算法是一致的,但是建立class_to_size和size_to_index结果的算法,却不一样。jemalloc用宏的方式,显示指定每个区间的步长的。tcmalloc的算法比较先进,下面是他的算法,详细参见gperftools的common.cc文件的AlignmentForSize函数:

1、如果Size > MaxSize,那么就以PageSize对齐

2、如果Size >= 128 ,从32开始,以2的倍数,增长步长。如下表

序号区间步长
0[128 --> 256)32
1[256 --> 512)64
2[512 --> 1024)128
3[1024 --> 2048)256
4[2048 --> 4096)512
5[4096 --> 8192)1024
6[8192 --> 16384)2048
7[16384 --> 32768)4096
8[32768 --> 65536)8192
9[65536 --> 131072)16384
10[131072 --> 262144)32768

这种算法见LgFloor函数,他的主要原则就是保证内存浪费利率控制1/8。因此,步长/区间上限=1/8。


3、如果Size >= 16,那么按16对齐

4、小于16就按8对齐。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值