DSP

内存管理之含对齐功能的分配器

2019-07-13 16:20发布

       本文是对游戏引擎架构(Jason Gregory)这本书中第五章游戏支持系统中含对齐功能的分配器的总结。        每个变量和数据对象都有对齐要求。8位整数可对齐至任何地址,但是32位整数或浮点变量必须4字节对齐,即其地址的最低有效半字节必须为0x0、0x4、0x8、0xC。128位SIMD矢量值通常需要16字节对齐,即其地址的最低有效半字节必须为0x0。        所有内存分配器都必须传回对齐的内存块。只要在分配内存时,分配比请求所需多一点的内存,再向上调整其内存地址至适当的对齐,最后传回调整后的地址。由于我们分配了多一点的内存,即使把地址往上调,传回的内存块仍然足够大。        在大多数实现中,额外分配的字节等于对齐字节。例如,假如请求16字节对齐的内存块,就可以额外分配多16字节。最坏的情况要把地址往上移动15个字节。多出的一个字节,促使我们在任何情况下都可使用相同的计算,就算原来分配的内存已经对齐。这样做简化并加速了代码,其代价是每次分配浪费1字节。另一方面,我们需要多出的这些字节以存储释放内存时所需的额外信息。 计算调整偏移量的方法如下:        首先用掩码(mask)把原本内存块地址的最低有效位取出,再把期望的对齐减去此值,结果就是调整偏移量。对齐总该是2的幂(通常是4或16字节),因此要计算掩码,只要把对齐减1就行了。
例:假如请求16字节对齐的内存块,掩码就是(16-1)=15=0x0000000F。把未对其的地址与掩码进行位并AND运算,就可得到错位(misalignment)的字节数目。        如果原来分配到的内存块地址为0x50341233,位并掩码0x0000000F后,得出0x00000003,即还差三字节就能对齐。要把这个地址对齐,只要加上(对其字节-错位字节)=(16-3)=13=0xD字节,因此最终对齐地址为0x50341233+0xD=0x50341240
//对齐分配函数。注意:“alignment”必须为2的幂(一般是4或16) void *allocateAligned(U32 size_bytes, U32 alignment) { //计算总共要分配的内存量 U32 expandedSize_bytes=size_bytes+alignment; //分配未对其的内存块,并转换为U32类型 U32 rawAddress=(U32)allocateUnaligned(expandedSize_bytes); //使用掩码去除地址低位部分,计算“错位量”,从而计算调整量 U32 mask=(alignment-1); U32 misalignmnet=(rawAddress & mask); U32 adjustment=alignment-misalignment; //计算调整后的地址,并把它以指针类型返回 U32 alignmentAddress=rawAddress+adjustment; return (void*)alignedAddress; }

那么当稍后我们要释放此内存块时,代码会传给分配器调整后的地址,而非原本我们分配的地址。那么,怎样才可以释放原本分配的内存呢?即我们要找到一种方法,把调整后的地址转换为原本的、可能未对齐的地址。 要完成这个转换,我们可以存储一些元信息(meta-information)至额外分配的内存,这些内存原来只是做对齐之用。最少的调整量为1个字节,这1字节足够存储偏移量(因为偏移量永不会超过256)。我们可以把偏移量存储至调整后地址之前的1字节,这么做,我们就可以简单地从调整后地址取回偏移量,并计算原本的地址,一下是修改后的allocateAligned()函数。 //对齐分配函数。注意:“alignment”必须为2的幂(一般是4或16) void *allocateAligned(U32 size_bytes, U32 alignment) { //若alignment==1,使用方必须调用allocateUnaligned()及freeUnaligned() ASSERT(alignment>1); //计算总共要分配的内存量 U32 expandedSize_bytes=size_bytes+alignment; //分配未对其的内存块,并转换为U32类型 U32 rawAddress=(U32)allocateUnaligned(expandedSize_bytes); //使用掩码去除地址低位部分,计算“错位量”,从而计算调整量 U32 mask=(alignment-1); U32 misalignmnet=(rawAddress & mask); U32 adjustment=alignment-misalignment; //计算调整后的地址,并把它以指针类型返回 U32 alignmentAddress=rawAddress+adjustment; //把alignment存储在调整后地址的前4字节 U32* pAdjustment=(U32*)(alignedAddress - 4); *pAdjustment=adjustment; return (void*)alignedAddress; } //而对应的freeAligned()函数可实现如下: void freeAligned(void *p) { U32 alignedAddress=(U32)p; U8* pAdjustment=(U8*)(alignedAddress - 4); U32 adjustment=(U32)*pAdjustment; U32 rawAddress=alignedAddress-adjustment; freeUnaligned((void*)rawAddress); }