ue4内存管理


ue4内存管理

自定义内存管理

 

 

ue4的内存管理主要是通过FMalloc类型的GMalloc这一结构来完成特定的需求,这是一个虚基类,它定义了malloc,realloc,free等一系列常用的内存管理操作。其中,Malloc的两个参数分别是分配内存的大小和对应的对齐量,默认对齐量为0。

/** The global memory allocator's interface. */
class CORE_API FMalloc  : 
 public FUseSystemMallocForNew,
 public FExec
{
public:
 virtual void* Malloc( SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT ) = 0;
 virtual void* TryMalloc( SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT );
 virtual void* Realloc( void* Original, SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT ) = 0;
 virtual void* TryRealloc(void* Original, SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT);
 virtual void Free( void* Original ) = 0;
  
 // ...
};

FMalloc有许多不同的实现,如FMallocBinned,FMallocBinned2等,可以在HAL文件夹下找到相关的头文件和定义,如下:

 

 

内部通过枚举量来确定对应使用的Allocator:

 /** Which allocator is being used */
 enum EMemoryAllocatorToUse
 {
  Ansi, // Default C allocator
  Stomp, // Allocator to check for memory stomping
  TBB, // Thread Building Blocks malloc
  Jemalloc, // Linux/FreeBSD malloc
  Binned, // Older binned malloc
  Binned2, // Newer binned malloc
  Binned3, // Newer VM-based binned malloc, 64 bit only
  Platform, // Custom platform specific allocator
  Mimalloc, // mimalloc
 };

对于不同平台而言,都有自己对应的平台内存管理类,它们继承自FGenericPlatformMemory,封装了平台相关的内存操作。具体而言,包含FAndroidPlatformMemory,FApplePlatformMemory,FIOSPlatformMemory,FWindowsPlatformMemory等。

通过调用PlatformMemory的BaseAllocator函数,我们取得平台对应的FMalloc类型,基类默认返回默认的C allocator,而不同平台会有自己特殊的实现。

在PlatformMemory的基础上,为了方便调用,ue4又封装了FMemory类,定义通用内存操作,如在申请内存时,会调用FMemory::Malloc,FMemory内部又会继续调用GMalloc->Malloc。如下为节选代码:

struct CORE_API FMemory
{
 /** @name Memory functions (wrapper for FPlatformMemory) */

 static FORCEINLINE void* Memmove( void* Dest, const void* Src, SIZE_T Count )
 {
  return FPlatformMemory::Memmove( Dest, Src, Count );
 }

 static FORCEINLINE int32 Memcmp( const void* Buf1, const void* Buf2, SIZE_T Count )
 {
  return FPlatformMemory::Memcmp( Buf1, Buf2, Count );
 }

 // ...
 static void* Malloc(SIZE_T Count, uint32 Alignment = DEFAULT_ALIGNMENT);
 static void* Realloc(void* Original, SIZE_T Count, uint32 Alignment = DEFAULT_ALIGNMENT);
 static void Free(void* Original);
 static SIZE_T GetAllocSize(void* Original);

    // ...
};

为了在调用new/delete能够调用ue4的自定义函数,ue4内部替换了operator new。这一替换是通过IMPLEMENT_MODULE宏引入的:

 

 

IMPLEMENT_MODULE通过定义REPLACEMENT_OPERATOR_NEW_AND_DELETE宏实现替换,如下图所示,operator new/delete内实际调用被替换为FMemory的相关函数。

 

 

FMallocBinned

我们以FMallocBinned为例介绍ue4中通用内存的分配。

基本介绍

(1) 空闲内存如何管理?

FMallocBinned使用freelist机制管理空闲内存。每个空闲块的信息记录在FFreeMem结构中,显式存储。

(2)不同大小内存如何分配?

FMallocBinned使用内存池机制,内部包含POOL_COUNT(42)个内存池和2个扩展的页内存池;其中每个内存池的信息由FPoolInfo结构体维护,记录了当前FreeMem内存块指针等,而特定大小的所有内存池由FPoolTable维护;内存池内包含了内存块的双向链表。

(3)如何快速根据分配元素大小找到对应的内存池?

为了快速查询当前分配内存大小应该对应使用哪个内存池,有两种办法,一种是二分搜索O(logN),另一种是打表(O1),考虑到可分配内存数量并不大,MallocBinned选择了打表的方式,将信息记录在MemSizeToPoolTable。

(4)如何快速删除已分配内存?

为了能够在释放的时候以O(1)时间找到对应内存池,FMallocBinned维护了PoolHashBucket结构用于跟踪内存分配的记录。它组织为双向链表形式,存储了对应内存块和键值。

内存池

● 多个小对象内存池(内存池大小均为PageSize,但存储的数据量不一样)。数据块大小设定如下:

 

 

● 两个额外的页内存池,管理大于一个页的内存池,大小为3*PageSize和6*PageSize

● 操作系统的内存池

分配策略

分配内存的函数为void* FMallocBinned::Malloc(SIZE_T Size, uint32 Alignment)。

其中第一个参数为需要分配的内存的大小,第二个参数为对齐的内存数。

如果用户未指定对齐的内存大小,MallocBinned内部会默认对齐于16字节,如果指定了大于16字节的对齐内存大小,则对齐于用户指定的对齐大小。根据对齐量,计算出最终实际分配的内存大小。

MallocBinned内部对于不同的内存大小有三种不同的处理:

(1) 分配小块内存(0,PAGE_SIZE_LIMIT/2)

根据分配大小从MemSizeToPoolTable中获取对应内存池,并从内存池的当前空闲位置读取一块内存,并移动当前内存指针。如果移动后的内存指针指向的内存块已经使用,则将指针移动到FreeMem链表的下一个元素;如果当前内存池已满,将该内存池移除,并链接到耗尽的内存池。

如果当前内存池已经用尽,下次内存分配时,检测到内存池用尽,会从系统重新申请一块对应大小的内存池。

(2) 分配大块内存 [PAGE_SIZE_LIMIT/2, PAGE_SIZE_LIMIT*3/4]∪(PageSize,PageSize + PAGE_SIZE_LIMIT/2)

需要从额外的页内存池分配,分配方式和(1)一样。

(3) 分配超大内存

从系统内存池中分配。

Allocator

对于ue4中的容器而言,它的模板有两个参数,第一个是元素类型,第二个就是对应的分配器(Allocator):

template<typename InElementType, typename InAllocator>
class TArray
{
   // ...
};

如下图,容器一般都指定了自己默认的分配器:

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM