下面来看一下TBB内存分配和回收源代码
/*TBB采用128B来对齐,虽然现在intel的处理cache采用64B来对齐,这里可能是为了以后的兼容性。也就是说分配的内存块的起始地址一定是128的倍数, NFS_LineSize一定的2的幂, 之所以要使用128B来对齐是为了用户请求的两个内存不可能被同一行cache给缓存,防止出现伪共享*/ static size_t NFS_LineSize = 128; size_t NFS_GetLineSize() { return NFS_LineSize; }
//这是进行内存分配的函数。 // @param : n 请求分配的内存块数量 // @param : element_size,用户请求的每块的内存大小 // @return: 返回给用户的内存地址。 void* NFS_Allocate( size_t n, size_t element_size, void* /*hint*/ ) { size_t m = NFS_LineSize; //两个assert是判断NFS_LineSize是否是2的幂 __TBB_ASSERT( m<=NFS_MaxLineSize, "illegal value for NFS_LineSize" ); __TBB_ASSERT( (m & (m-1))==0, "must be power of two" ); //计算用户总共需要的内存大小 size_t bytes = n*element_size; //判断请求的内存大小是否合法 if (bytes<n || bytes+m<bytes) { // Overflow throw_exception(eid_bad_alloc); } // scalable_aligned_malloc considers zero size request an error, and returns NULL if (bytes==0) bytes = 1; //padded_allocate_handler是一个函数指针,这里指向 padded_allocate void* result = (*padded_allocate_handler)( bytes, m ); //如果分配失败则抛出异常 if (!result) throw_exception(eid_bad_alloc); //判断返回用户的内存地址块是否是NFS_LineSize对齐的,NFS_LineSize是2的幂 __TBB_ASSERT( ((size_t)result&(m-1)) == 0, "The address returned isn't aligned to cache line size" ); return result; }
//释放内存 void NFS_Free( void* p ) { (*padded_free_handler)( p ); }
//进行内存分配 // @param : bytes 用户实际需要的内存大小 // @param : alignment 分配算法采用的对齐大小, 传入的值一般是NFS_LineSize。 static void* padded_allocate( size_t bytes, size_t alignment ) { unsigned char* result = NULL; //向系统请求分配alignment+bytes的内存块,注意要对请求alignment大小的内存,是为了下面进行内存对齐, unsigned char* base = (unsigned char*)malloc(alignment+bytes); if( base ) { // Round up to the next line // 这里是进行内存对齐,这里采用了一个非常巧妙的方法,在后方对其进行详细解释 result = (unsigned char*)((uintptr_t)(base+alignment)&-alignment); // Record where block actually starts. // 这里将malloc分配的起始地址写入result前的4个字节中,由于alignment > 4且malloc分配的地址是4字节对齐\ // 故而result前一定含有至少4个空字节,记录base的起始是为了释放时使用 ((uintptr_t*)result)[-1] = uintptr_t(base); } return result; }
// 释放内存 // @param : p 函数NFS_Allocate返回给用户的内存地址 static void padded_free( void* p ) { if( p ) { // 0x4096以下的内存空间由系统使用,不对外分配,故而不能释放 __TBB_ASSERT( (uintptr_t)p>=0x4096, "attempt to free block not obtained from cache_aligned_allocator" ); // Recover where block actually starts // 取出NFS_Allocate分配的该内存块的真实起始地址,该地址是由malloc返回的 unsigned char* base = ((unsigned char**)p)[-1]; // 进行真实块地址的合法性判断,对应于NFS_Allocate采用的字节对齐 __TBB_ASSERT( (void*)((uintptr_t)(base+NFS_LineSize)&-NFS_LineSize)==p, "not allocated by NFS_Allocate?" ); free(base); } }
result = (unsigned char*)((uintptr_t)(base+alignment)&-alignment);
这个NFS_LineSize的对齐就是由上面的公式计算出来的。由于base是4的倍数, alignment也是2的幂,假设alignment=2^k。
我们将上式进行简写一下:
r = (n + m)& (-m).
下面使用二进制表示,X代表0或者1
n = XX...XX00
m = 0...010...0, 1后总共m个0
-m= 1...110...0, 补码表示
故而(n + m) & (-m) 一定是XX...XXX0..0, 即最后含有至少m个0, 所以r是m的倍数。
综合上面的分析,其算法流程可以归纳如下:
1. 用户申请n块大小为m的内存空间。
2. 计算用户申请的内存空间大小s = m * n, 使用malloc函数申请 s + NFS_LineSize的内存空间,起始内存地址为base
3. 计算从base开始(不包含base)的第一个NFS_LineSize的倍数地址result。
4. 将base写入result前的4个字节中。
5. 将result返回给用户。