obs-studio源码中关于内存管理的代码在bmem.h和bmem.c中。其内存管理的特点总结下来,有一下几点:
- 跨平台
- 内存对齐,提高存取效率
- 提供了内存申请释放的统一的C接口
最大的亮点就是内存管理是经过内存对齐的,那我们首先讲讲什么是内存对齐。
内存对齐的原因?(百度百科)
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
以下图为例,从地址0处读取4字节,1次读取即可。从地址1处读取4字节,需要读取2次。
读取2次时,还要进行如下的合并操作。将 0-3 的数据向上偏移 1 字节,将 4-7 的数据向下偏移 3 字节。最后再将两块数据合并放入寄存器。
原因就是,
CPU 并不是以字节为单位存取数据的。每次内存存取都会产生一个固定的开销,减少内存存取次数将提升程序的性能。所以 CPU 一般会以 2/4/8/16/32 字节为单位来进行存取操作。我们将上述这些存取单位称为内存存取粒度。
内存对齐规则
-
数据成员对齐规则:struct 或 union (以下统称结构体)的数据成员,第一个数据成员放在偏移为 0 的地方,以后每个数据成员的偏移为 #pragma pack 指定的数值和这个数据成员自身长度中较小那个的整数倍。
-
数据成员为结构体:如果结构体的数据成员还为结构体,则该数据成员的“自身长度”为其内部最大元素的大小。(struct a 里存有 struct b,b 里有char,int,double等元素,那 b “自身长度”为 8)
-
结构体的整体对齐规则:在数据成员按照 #1 完成各自对齐之后,结构体本身也要进行对齐。对齐会将结构体的大小增加为 #pragma pack 指定的数值和结构体最大数据成员长度中较小那个的整数倍。
注:#pragma pack (n) 表示设置为 n 字节对齐。 Xcode 默认为 8 字节对齐。当设置为 #pragma pack (1) 时就代表不进行内存对齐,上述代码打印的结果就都为 8。
obs-studio内存管理源码分析
// 内存管理器
struct base_allocator {
void *(*malloc)(size_t);
void *(*realloc)(void *, size_t);
void (*free)(void *);
};
这又是一个C语言实现的类似多态的结构体,根据对函数指针的实际赋值,动态决定如何进行内存申请和释放。
那么接下来,就是如何通过实际malloc、realloc、free的定义来实现跨平台和内存的效果了。
直接上粘源码片段吧,代码中已经把注释写的很详细了,大家自己看吧。
/*
* NOTE: totally jacked the mem alignment trick from ffmpeg, credit to them:
* 内存对齐归功于ffmpeg
* http://www.ffmpeg.org/
*/
// 通过4字节(32位)进行对齐
#define ALIGNMENT 32
/* TODO: use memalign for non-windows systems */
#if defined(_WIN32)
#define ALIGNED_MALLOC 1
#else
#define ALIGNMENT_HACK 1
#endif
static void *a_malloc(size_t size)
{
#ifdef ALIGNED_MALLOC
// Windows平台提供了基于内存对齐的内存申请接口
return _aligned_malloc(size, ALIGNMENT);
#elif ALIGNMENT_HACK
void *ptr = NULL;
long diff;
// 多申请4字节内存
ptr = malloc(size + ALIGNMENT);
if (ptr) {
// 计算内存对齐偏移量
diff = ((~(long)ptr) & (ALIGNMENT - 1)) + 1;
// 起始位置调整
ptr = (char *)ptr + diff;
// 记录内存对齐偏移量,方便后续释放内存使用
((char *)ptr)[-1] = (char)diff;
}
return ptr;
#else
return malloc(size);
#endif
}
// 注:我们默认ptr是通过a_malloc申请的内存指针
static void *a_realloc(void *ptr, size_t size)
{
#ifdef ALIGNED_MALLOC
// Windows平台提供了基于内存对齐的realloc内存申请接口
return _aligned_realloc(ptr, size, ALIGNMENT);
#elif ALIGNMENT_HACK
long diff;
if (!ptr)
return a_malloc(size);
// 获取内存对齐偏移量
diff = ((char *)ptr)[-1];
// realloc内存,注意,ptr一定要指向实际内存的起始地址
ptr = realloc((char *)ptr - diff, size + diff);
if (ptr)
ptr = (char *)ptr + diff;
return ptr;
#else
return realloc(ptr, size);
#endif
}
static void a_free(void *ptr)
{
#ifdef ALIGNED_MALLOC
_aligned_free(ptr);
#elif ALIGNMENT_HACK
if (ptr)
// 释放内存
// 细节:被free的指针指向一定是ptr减去对齐偏移量的,否则会造成内存泄露
free((char *)ptr - ((char *)ptr)[-1]);
#else
free(ptr);
#endif
}
// 在此明确malloc、relloc和free的实际实现
static struct base_allocator alloc = {a_malloc, a_realloc, a_free};
据obs作者所说,将内存对齐发扬光大的是ffmpeg,ffmpeg的源代码也是非常优秀的,C语言中透露着面向对象的影子。大家有空可以自己去研究一下。
参考资料:
【1】内存对齐
【2】内存对齐