【学习体会】aligned_malloc实现内存对齐

什么是内存对齐?有两种解释:

  1. 存放数据的首地址是某个数(通常它为4,8或者32)的倍数。
  2. 数据结构所占字节数是某个数(通常它为4,8或者32)的倍数。

对于2,举个最简单的例子:

struct{
    int x;
    char y;
}s;

理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这是编译器自动进行了内存对齐

而我们想要说的是第 1 种解释:

存放数据的首地址是某个数(通常它为4,8或者32)的倍数。

 大家都知道C++中可以直接调用malloc请求内存被返回分配成功的内存指针,该指针指向的地址就是分配得到的内存的起始地址。比如,在VS中选择x86平台,该平台是针对32位系统进行编译的,

int main()
{
    void *p = malloc(1024);
    printf("0x%p\n", p);
    free(p);
}

HEX:0x0110BFC8

DEC:17,874,888

在请求了一个大小为1024的内存块并打印出来,但是这块内存的地址并不是32的倍数。

17,874,888 / 32 = 558,590.25

那么为什么要内存对齐32字节呢?

因为我们如果用到了simd256技术的话,simd一种单指令多数据的数据,而256就是指同时操作256bit的数据,而256bit=32byte。因此simd256技术要求所操作的数据的首地址是内存对齐32字节。

这样的话,原先的malloc就不够用了,因为它不能分配给我们内存对齐于32字节的。

在网上一直有下面这个aligned_malloc的实现,不知道具体出处,本文对这个函数进行详细分析,

先看具体实现:

#include<iostream>

void* aligned_malloc(size_t size, int alignment)
{
	// 分配足够的内存, 这里的算法很经典, 早期的STL中使用的就是这个算法  

	// 首先是维护FreeBlock指针占用的内存大小  
	const int pointerSize = sizeof(void*);

	// alignment - 1 + pointerSize这个是FreeBlock内存对齐需要的内存大小  
	// 前面的例子sizeof(T) = 20, __alignof(T) = 16,  
	// g_MaxNumberOfObjectsInPool = 1000  
	// 那么调用本函数就是alignedMalloc(1000 * 20, 16)  
	// 那么alignment - 1 + pointSize = 19  
	const int requestedSize = size + alignment - 1 + pointerSize;

	// 分配的实际大小就是20000 + 19 = 20019  
	void* raw = malloc(requestedSize);

	// 这里实Pool真正为对象实例分配的内存地址  
	uintptr_t start = (uintptr_t)raw + pointerSize;
	// 向上舍入操作  
	// 解释一下, __ALIGN - 1指明的是实际内存对齐的粒度  
	// 例如__ALIGN = 8时, 我们只需要7就可以实际表示8个数(0~7)  
	// 那么~(__ALIGN - 1)就是进行舍入的粒度  
	// 我们将(bytes) + __ALIGN-1)就是先进行进位, 然后截断  
	// 这就保证了我是向上舍入的  
	// 例如byte = 100, __ALIGN = 8的情况  
	// ~(__ALIGN - 1) = (1 000)B  
	// ((bytes) + __ALIGN-1) = (1 101 011)B  
	// (((bytes) + __ALIGN-1) & ~(__ALIGN - 1)) = (1 101 000 )B = (104)D  
	// 104 / 8 = 13, 这就实现了向上舍入  
	// 对于byte刚好满足内存对齐的情况下, 结果保持byte大小不变  
	// 记得《Hacker's Delight》上面有相关的计算  
	// 这个表达式与下面给出的等价  
	// ((((bytes) + _ALIGN - 1) * _ALIGN) / _ALIGN)  
	// 但是SGI STL使用的方法效率非常高   
	void* aligned = (void*)((start + alignment - 1) & ~(alignment - 1));

	// 这里维护一个指向malloc()真正分配的内存  
	*(void**)((uintptr_t)aligned - pointerSize) = raw;

	// 返回实例对象真正的地址  
	return aligned;
}


// 这里是内部维护的内存情况  
//                   这里满足内存对齐要求  
//                             |  
// ----------------------------------------------------------------------  
// | 内存对齐填充 | 维护的指针 | 对象1 | 对象2 | 对象3 | ...... | 对象n |  
// ----------------------------------------------------------------------  
// ^                     | 指向malloc()分配的地址起点  
// |                     |  
// -----------------------  
template<typename T>
void aligned_free(T * aligned_ptr)
{
	if (aligned_ptr)
	{
		free(((T**)aligned_ptr)[-1]);
	}
}

bool isAligned(void* data, int alignment)
{
	// 又是一个经典算法, 参见<Hacker's Delight>  
	return ((uintptr_t)data & (alignment - 1)) == 0;
}

void main() {
	int totalsize = 10;
	int* data = (int*)aligned_malloc(sizeof(int)*totalsize, 32);

	if (isAligned(data, 32)) {
		std::cout << "isAligned\n";
	}
	memset(data, 0, sizeof(int)*totalsize);
	data[5] = 1;

	for (int i = 0; i < totalsize; i++) {
		std::cout << data[i] << " ";
	}
	std::cout << "\n";
	aligned_free<int>(data);
	std::cout << "aligned_free\n";
	while(1){}
}

假设我们需要开辟一块内存,字节数为size,如果使用malloc,那么返回的内存指针为raw,这个指针指向的地址不一定是32的倍数,因此我们需要多一点,比如多alignment – 1 + pointerSize

 

 requestedSize就是我们为了能够内存对齐而申请的内存大小,会比实际需要的内存alignment – 1 + pointerSize。alignment是对齐量,比如32;pointerSize是指针类型占内存的字节数(简单说就是存放一个指针需要多少个字节),这个通常和电脑操作系统和编译器有关。

这段代码解决的问题就是,在这个大小为requestedSize的内存块内,找到对齐32的地址并返回。

解决方法如图:

其中的aligned就是对齐32的地址。

例如:

假设,start = 100, alignment= 32的情况

alignment = (0010 0000)B

alignment - 1 = (0001 1111)B

~(alignment - 1) = (1110 0000)B

size = (0110 0100)B

(size +alignment - 1) = (1000 0011)B

(size +alignment - 1) & ~(alignment - 1) =  (1000 0000)B = (128)D

128 / 32 = 4, 这个地址可以对齐32位。

可以尝试不同的start,结果都是一样的,非常有意思的算法。

据代码的作者说,这是来自《Hacker's Delight》,早期STL也是这么实现的。

同时,非常重要的一点是,后续怎么释放掉这一块内存,

*(void**)((uintptr_t)aligned - pointerSize) = raw;

 在aligned的左边pointerSize的位置,存放raw指针,这样后续我们就可以用

((void**)aligned_ptr)[-1] 获取到raw指针,从而可以调用free对内存继续释放。

template<typename T>
void aligned_free(T * aligned_ptr)
{
	if (aligned_ptr)
	{
		free(((T**)aligned_ptr)[-1]);
	}
}

最后,代码作者也给出判断地址是否对齐的算法:

bool isAligned(void* data, int alignment)
{
	// 又是一个经典算法, 参见<Hacker's Delight>  
	return ((uintptr_t)data & (alignment - 1)) == 0;
}

 

参考博文:

C/C++内存对齐详解 - 知乎

aligned_malloc及aligned_free的实现及详细解释_John_Jane_Doe的博客-CSDN博客_aligned_malloc

在C++中实现aligned_malloc - 老胡写代码 - 博客园 

关于VS项目平台的x86,x64,Any CPU以及Debug和Release的区别 - 李华丽 - 博客园 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值