UE4支持多种内存分配器:
/** 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 };
具体包括如下几类:
Ansi内存分配器(标准C):直接调用malloc、free、realloc函数
TBB(Thread Building Blocks)内存分配器:Intel 提供的第三方库的一个可伸缩内存分配器(Scalable Memory Allocator)
Jemalloc内存分配器(Linux / FreeBSD):适合多线程下的内存分配管理 http://www.canonware.com/jemalloc/
Stomp:用于查非法内存操作(如:内存越界,野指针)的管理方式,目前只支持windows、mac、unix等pc平台。带命令行参数-stompmalloc来启用该分配器
Mimalloc:https://github.com/microsoft/mimalloc
UE4内置的内存分配器
① Binned(第一代箱式内存分配器)
② Binned2(第二代箱式内存分配器)
③ Binned3(第三代箱式内存分配器,仅支持64bits)
对于不同平台而言,都有自己对应的平台内存管理类,它们继承自FGenericPlatformMemory,封装了平台相关的内存操作。
具体而言,包含FAndroidPlatformMemory,FApplePlatformMemory,FIOSPlatformMemory,FWindowsPlatformMemory、FLinuxPlatformMemory、FUnixPlatformMemory、FMacPlatformMemory等。
通过调用FPlatformMemory的BaseAllocator函数,我们取得平台对应的FMalloc类型,基类默认返回默认的Ansi内存分配器(标准C),而不同平台会有自己特殊的实现。
如果想知道当前平台使用了哪种分配器,可以查看对应的PlatformMemory文件。例如WindowsPlatformMemory.cpp文件,找到BaseAllocator函数,会发现Windows平台默认使用了TBB分配器。
在FMemory::Malloc申请内存时,会先判断FMalloc* GMalloc(全局的内存分配器)是否已创建,如果没有创建,void FMemory::GCreateMalloc() --》int FMemory_GCreateMalloc_ThreadUnsafe() --》FMalloc* FPlatformMemory::BaseAllocator()来初始化GMalloc
FMemory是一个静态工具类,对FMalloc* GMalloc进行了封装,具体逻辑详见:
UnrealEngineEngineSourceRuntimeCorePublicHALUnrealMemory.h
UnrealEngineEngineSourceRuntimeCorePublicHALFMemory.inl
UnrealEngineEngineSourceRuntimeCorePrivateHALUnrealMemory.cpp
Ansi | TBB | Jemalloc | Binned | Binned2 | Binned3 | Mimalloc | Stomp | |
Android | 支持 | 支持 | 默认 | 支持(64bits) | ||||
IOS | 支持 | 默认 | ||||||
Windows | 支持 | 默认 | 支持 | 支持 | 支持(64bits) | 支持 | 支持 | |
Linux | 支持 | 支持 | 支持 | 默认 | 支持 | |||
Mac |
支持 |
默认 | 支持 | 支持 | 支持 | |||
HoloLens |
支持 |
默认 |
Android下可在/storage/emulated/0/UE4Game目录中放置flag文件来动态启用使用哪种内存分配器:
FMalloc* FAndroidPlatformMemory::BaseAllocator() { // ... ... if (access("/storage/emulated/0/UE4Game/binned3.flag", 0) == 0) { return new FMallocBinned3(); } // ... ... }
注1:FUseSystemMallocForNew实现new、new[]、delete、delete[]操作符,主要是为了防止new FMalloc对象时调用到全局的new、new[]、delete、delete[],导致死循环
注2:在游戏线程中,可显示调用Trim函数来释放未被使用的内存页。另外,在gc mark时会调用该函数。 -- FMallocBinned没有实现该函数
注3:FMalloc逻辑详见:UnrealEngineEngineSourceRuntimeCorePublicHALMemoryBase.h、UnrealEngineEngineSourceRuntimeCorePrivateHALMemoryBase.cpp
游戏启动时,会打印出当前所用的内存分配器(Alloctator) 详见:void FApp::PrintStartupLogMessages()函数
LogInit: WinSock: version 1.1 (2.2), MaxSocks=32767, MaxUdp=65467
LogOnline: OSS: TryLoadSubsystemAndSetDefault: Loaded subsystem for module [NULL]
LogInit: Build: ++UE4+Release-4.26-CL-0
LogInit: Engine Version: 4.26.1-0+++UE4+Release-4.26
LogInit: Compatible Engine Version: 4.26.0-0+++UE4+Release-4.26
LogInit: Net CL: 0
LogInit: OS: Windows 10 (Release 1903) (), CPU: Intel(R) Core(TM) i9-9900 CPU @ 3.10GHz, GPU: NVIDIA GeForce RTX 2080
LogInit: Compiled (64-bit): Mar 25 2021 19:11:23
LogInit: Compiled with Visual C++: 19.27.29111.00
LogInit: Build Configuration: Debug
LogInit: Branch Name: ++UE4+Release-4.26
LogInit: Command Line: ThirdPersonExampleMap -messaging -Windowed ResX=800 ResY=600 -game -skipcompile -debug
LogInit: Base Directory: G:/svn/UnrealEngine/Engine/Binaries/Win64/
LogInit: Allocator: TBB
LogInit: Installed Engine Build: 0
内存体系结构
重写全局new、delete
详见:UnrealEngineEngineSourceRuntimeCorePublicModulesBoilerplateModuleBoilerplate.h
OPERATOR_NEW_MSVC_PRAGMA void* operator new ( size_t Size ) OPERATOR_NEW_THROW_SPEC { return FMemory::Malloc( Size ); } // 正常版本new OPERATOR_NEW_MSVC_PRAGMA void* operator new[]( size_t Size ) OPERATOR_NEW_THROW_SPEC { return FMemory::Malloc( Size ); } // 正常版本new[] OPERATOR_NEW_MSVC_PRAGMA void* operator new ( size_t Size, const std::nothrow_t& ) OPERATOR_NEW_NOTHROW_SPEC { return FMemory::Malloc( Size ); } // 兼容早期版本的new,内存分配失败不会抛出异常 OPERATOR_NEW_MSVC_PRAGMA void* operator new[]( size_t Size, const std::nothrow_t& ) OPERATOR_NEW_NOTHROW_SPEC { return FMemory::Malloc( Size ); } // 兼容早期版本的new[],内存分配失败不会抛出异常 void operator delete ( void* Ptr ) OPERATOR_DELETE_THROW_SPEC { FMemory::Free( Ptr ); } // 正常版本delete void operator delete[]( void* Ptr ) OPERATOR_DELETE_THROW_SPEC { FMemory::Free( Ptr ); } // 正常版本delete[] void operator delete ( void* Ptr, const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } // 兼容早期版本的delete,内存释放失败不会抛出异常 void operator delete[]( void* Ptr, const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } // 兼容早期版本的delete,内存释放失败不会抛出异常 void operator delete ( void* Ptr, size_t Size ) OPERATOR_DELETE_THROW_SPEC { FMemory::Free( Ptr ); } // 正常版本delete(带size) void operator delete[]( void* Ptr, size_t Size ) OPERATOR_DELETE_THROW_SPEC { FMemory::Free( Ptr ); } // 正常版本delete[](带size) void operator delete ( void* Ptr, size_t Size, const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } // 兼容早期版本的delete(带size),内存释放失败不会抛出异常 void operator delete[]( void* Ptr, size_t Size, const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } // 兼容早期版本的delete[](带size),内存释放失败不会抛出异常
裸指针 new / delete
{ int32* p1 = new int32; delete p1; }
申请内存时:
释放内存时:
裸指针 new [] / delete []
{ float* p2 = new float[5]; delete[] p2; }
申请内存时:
释放内存时:
智能指针 new / delete
{ TUniquePtr<double> up1(new double(10.8)); }
申请内存时:
释放内存时:
智能指针 new[] / delete[]
{ TUniquePtr<char[]> up2 = MakeUnique<char[]>(25); }
申请内存时:
释放内存时:
UE4容器
TArray缺省内存分配器为FDefaultAllocator --》TSizedDefaultAllocator<32> --》TSizedHeapAllocator
TMap、TSet缺省内存分配器为FDefaultSetAllocator --》TSetAllocator --》TSparseArrayAllocator<FDefaultAllocator,FDefaultBitArrayAllocator>
详见:UnrealEngineEngineSourceRuntimeCorePublicContainersContainersFwd.h
template<int IndexSize> class TSizedDefaultAllocator; using FDefaultAllocator = TSizedDefaultAllocator<32>; using FDefaultAllocator64 = TSizedDefaultAllocator<64>; class FDefaultSetAllocator; template<typename T, typename Allocator = FDefaultAllocator> class TArray; template<typename KeyType, typename ValueType, bool bInAllowDuplicateKeys> struct TDefaultMapHashableKeyFuncs; template<typename KeyType, typename ValueType, typename SetAllocator = FDefaultSetAllocator, typename KeyFuncs = TDefaultMapHashableKeyFuncs<KeyType, ValueType, false> > class TMap; template<typename KeyType, typename ValueType, typename SetAllocator = FDefaultSetAllocator, typename KeyFuncs = TDefaultMapHashableKeyFuncs<KeyType, ValueType, true > > class TMultiMap; template <typename T = void > struct TLess; template <typename> struct TTypeTraits; template<typename KeyType, typename ValueType, typename ArrayAllocator = FDefaultAllocator, typename SortPredicate = TLess<typename TTypeTraits<KeyType>::ConstPointerType> > class TSortedMap; template<typename ElementType,bool bInAllowDuplicateKeys = false> struct DefaultKeyFuncs; template<typename InElementType, typename KeyFuncs = DefaultKeyFuncs<InElementType>, typename Allocator = FDefaultSetAllocator> class TSet;
FString
{ FString str1 = TEXT("Hello World!");// 字符串长度为12,Realloc中NewSize值:(字符串长度+1) * 2 = 26 } // 离开作用域,调用Free释放内存
注:FString中包含一个TArray<TCHAR> Data的成员变量
申请内存时:
释放内存时:
TArray
{ TArray<int32> arr1; arr1.Add(12); // Add第一个元素时,ArrayMax为4,Realloc中NewSize值:16 注:TArray申请的空间要多一些,不会Add一个,申请一个的内存,当空间不够时会按照一定的算法来扩容 详见:DefaultCalculateSlackGrow函数 arr1.Add(13); } // 离开作用域,调用Free释放内存
申请内存时:
释放内存时:
TSet
{ TSet<double> set1; set1.Add(1.2); // Add第一个元素时,申请内存,Realloc中NewSize值:64 注:TSet申请的空间要多一些,不会Add一个,申请一个的内存,当空间不够时会按照一定的算法来扩容 set1.Add(2.5); } // 离开作用域,调用Realloc释放内存(NewSize传入0)
申请内存时:
释放内存时:
TMap
{ TMap<int32, int32> map1; map1.Add(1, 18);// Add第一个元素时,申请内存,Realloc中NewSize值:64 注:TMap申请的空间要多一些,不会Add一个,申请一个的内存,当空间不够时会按照一定的算法来扩容 map1.Add(2, 28); map1.Add(3, 38); } // 离开作用域,调用Realloc释放内存(NewSize传入0)
申请内存时:
释放内存时:
UObject
FString MyBPObjectPath = TEXT("/Game/ThirdPersonCPP/Blueprints/MyBlueprintObject.MyBlueprintObject_C"); UClass* MyBPObjectClass = LoadClass<UObject>(nullptr, *MyBPObjectPath); // MyBPObjectClass为UBlueprintGeneratedClass*类型 UMyBPObject* BPObj1 = NewObject<UMyBPObject>(this, MyBPObjectClass); // sizeof(UMyBPObject)为48 注:考虑到内存对齐,AllocateUObject时传入的Size为64 CollectGarbage(RF_NoFlags); // 全量阻塞gc
申请内存时:
释放内存时: