本文来分析一下ncnn中的基础数据结构mat以及内存分配allocator。
今天要分析的代码主要是ncnn/scr下的allocator.h、allocator.cpp、mat.h和mat.cpp。还是老样子,暂时不去看vulkan方面的代码。
allocator
先来看allocator.h,除去#if NCNN_VULKAN和#endif之间的内容剩下的就是与内存分配相关的类和函数。先来看Allocator基类:
class Allocator
{
public:
//Allocator基类析构函数
virtual ~Allocator();
//分配内存的函数,纯虚函数需要在子类中继承实现,输入需要分配的size
virtual void* fastMalloc(size_t size) = 0;
//释放内存的函数,纯虚函数需要在子类中继承实现,输入需要释放的内存的指针
virtual void fastFree(void* ptr) = 0;
};
Allocator有两个子类PoolAllocator和UnlockedPoolAllocator,一个是带锁的内存分配,一个是无锁的内存分配。当然两个类里都继承了fastMalloc和fastFree函数。接下来主要来分析这两个函数以及一些辅助函数。
在本文集的《内存对齐》中提到过,cpu在读取内存时是一块一块进行读取的,块的大小可以是2,4,8,16(总之是2的倍数),这里宏定义表示这里我们需要内存对齐的块大小为16,
#define MALLOC_ALIGN 16
这里把fastMalloc声明成静态内联函数,这个我谈谈自己的想法,首先,inline函数跟宏定义类似,不存在所谓的函数入口。如果只用inline,不加static,当包含inline函数的.h文件被不同的文件包含时,会出现重名。加了static后函数只能在文件内部可见,则不会有重名问题,相当于一个inline在不同的.c文件里生成了不同的实例。
static inline void* fastMalloc(size_t size)
{
//分配size + sizeof(void*) + MALLOC_ALIGN大的空间
unsigned char* udata = (unsigned char*)malloc(size + sizeof(void*) + MALLOC_ALIGN);
//是否分配成功
if (!udata)
return 0;
//得到对齐后的分配空间的地址,释放内存时用到
unsigned char** adata = alignPtr((unsigned char**)udata + 1, MALLOC_ALIGN);
//存储未对分配空间做对齐时的起始地址
adata[-1] = udata;
return adata;
#endif
}
首先,分配size + sizeof(void) + MALLOC_ALIGN大的空间,我们需要size大的空间,那多出来的sizeof(void) + MALLOC_ALIGN空间是做什么用的,sizeof(void)就是一个指针的大小,指针的本质就是内存地址(x86是4byte,x64是8byte),所以这里要分配一个内存地址的空间来存储内存地址,这个内存地址就是未对分配空间做对齐时的起始地址,那么多出来MALLOC_ALIGN的空间呢?也就是16byte的空间,这个空间最后是浪费的,但是它还是有作用的,它的作用是对size + sizeof(void) + MALLOC_ALIGN的空间做完对齐后,我们需要的size的这一部分内存空间的起始地址就不是原来我们分配的size + sizeof(void) + MALLOC_ALIGN的其实地址了,为了保证对齐之后的起始地址之后还有size大小的空间,所以我们还需要至少MALLOC_ALIGN的空间。
由于16长度画图太难,这里把它缩减为4长度。
下面我们分配了4长度的内存(蓝色部分)

对齐之后我们的起始地址变成0x4,我们还是要得到4长度的空间,那我们还是要分配的空间至少要原来分配的空间(蓝色部分)后面再加上对齐长度(这里是4)。

从上面的注释我们可以看到关键的一句在alignPtr函数这一行,这里把udata转换成(unsigned char )并加1。这个作用主要是在我们分配的size + sizeof(void) + MALLOC_ALIGN的空间开始位置留出一个8Byte的空间,用于存指针。也就是假如原来udata指向0x0,那么(unsigned char)udata + 1指向0x8,这个地址即为我们需要对齐后的size部分的空间的起始地址。
alignPtr函数主要作用是返回内存对齐后size那部分内存的起始地址。_Tp即为unsigned char *类型。这里n=16,&-n就是让指针指向16的整数倍以达到内存对齐的目的。
// Aligns a pointer to the specified number of bytes
// ptr Aligned pointer
// n Alignment size that must be a power of two
template<typename _Tp> static inline _Tp* alignPtr(_Tp* ptr, int n=(int)sizeof(_Tp))
{
return (_Tp*)(((size_t)ptr + n-1) & -n);
}
接下来是释放内存的函数fastFree,如果指针不为空,则把内存重新指向原来申请的总内存的起始地址,然后free掉。这里应该比较简单,不做过多分析。
static inline void fastFree(void* ptr)
{
if (ptr)
{
unsigned char* udata = ((unsigned char**)ptr)[-1];
free(udata);
}
}
这.h文件中还有一个函数
static inline size_t alignSize(size_t sz, int n)
{
return (sz + n-1) & -n;
}
他的主要作用就是内存对齐。n是2的幂次。
网友评论