在现代计算架构中,从内存中读取数据,基本上都是按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对齐。