Unity NativeArray 内存解析
本文要揭示NativeArray真的是使用Native Memory的整个过程.
代码来源:C#部分 C++部分来自于IDA pro解析UnityPlayer.dll
C#代码
先来看C#部分中的
public unsafe struct NativeArray<T> : IDisposable, IEnumerable<T>, IEquatable<NativeArray<T>> where T : struct
{
internal void* m_Buffer;
internal int m_Length;
是一个模板类
先来看下构造函数
public NativeArray(int length, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
{
Allocate(length, allocator, out this);
if ((options & NativeArrayOptions.ClearMemory) == NativeArrayOptions.ClearMemory)
UnsafeUtility.MemClear(m_Buffer, (long)Length * UnsafeUtility.SizeOf<T>());
}
public NativeArray(T[] array, Allocator allocator)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
Allocate(array.Length, allocator, out this);
Copy(array, this);
}
public NativeArray(NativeArray<T> array, Allocator allocator)
{
AtomicSafetyHandle.CheckReadAndThrow(array.m_Safety);
Allocate(array.Length, allocator, out this);
Copy(array, 0, this, 0, array.Length);
}
可以发现内存分配的函数就是Allocate
static void Allocate(int length, Allocator allocator, out NativeArray<T> array)
{
long totalSize = UnsafeUtility.SizeOf<T>() * (long)length;
CheckAllocateArguments(length, allocator, totalSize);
array = default(NativeArray<T>);
array.m_Buffer = UnsafeUtility.Malloc(totalSize, UnsafeUtility.AlignOf<T>(), allocator);
array.m_Length = length;
array.m_AllocatorLabel = allocator;
array.m_MinIndex = 0;
array.m_MaxIndex = length - 1;
DisposeSentinel.Create(out array.m_Safety, out array.m_DisposeSentinel, 1, allocator);
InitStaticSafetyId(ref array.m_Safety);
}
分配了具体的内存大小,发现关键代码是UnsafeUtility.Malloc(totalSize, UnsafeUtility.AlignOf(), allocator);
unsafe public static extern void* Malloc(long size, int alignment, Allocator allocator);
这是外部调用函数,可以从IDA pro中获得
C++部分
void *__cdecl UnsafeUtility_CUSTOM_Malloc(unsigned int a1, int a2, int a3, int a4)
{
int v4; // ecx
void *v5; // esi
struct IDSPGraph *v6; // eax
int v7; // eax
const char *v9; // [esp+4h] [ebp-40h]
char *v10; // [esp+8h] [ebp-3Ch]
char *v11; // [esp+Ch] [ebp-38h]
char *v12; // [esp+10h] [ebp-34h]
char *v13; // [esp+14h] [ebp-30h]
__int128 v14; // [esp+18h] [ebp-2Ch]
int v15; // [esp+28h] [ebp-1Ch]
int v16; // [esp+2Ch] [ebp-18h]
char v17; // [esp+30h] [ebp-14h]
char v18; // [esp+34h] [ebp-10h]
int v19; // [esp+3Ch] [ebp-8h]
int v20; // [esp+40h] [ebp-4h]
v4 = 16;
v19 = 0;
if ( a3 > 0 )
v4 = a3;
v20 = 0;
v5 = 0;
switch ( a4 )
{
case 2:
v5 = ManagedTempMemScope::Allocate(a1, v4);
break;
case 3:
v5 = malloc_internal(a1, v4, 2, 0, (int)&byte_111478C4, 71);
break;
case 4:
v5 = malloc_internal(a1, v4, 105, 0, (int)&byte_111478C4, 82);
break;
case 5:
v6 = GetIDSPGraph();
if ( v6 )
{
v5 = (void *)(**(int (__stdcall ***)(unsigned int))v6)(a1);
if ( !v5 )
{
v7 = Scripting::CreateInvalidOperationException(&v18, "Invalid context for allocating audio kernel memory");
ScriptingExceptionPtr::operator=(&v19, v7);
}
}
else
{
v10 = &byte_111478C4;
v11 = &byte_111478C4;
v12 = &byte_111478C4;
v17 = 1;
v9 = "DSPGraph module is not loaded";
v13 = &byte_111478C4;
v14 = _xmm;
v15 = 0;
v16 = 0;
DebugStringToFile((const struct DebugStringToFileData *)&v9);
}
break;
default:
break;
}
if ( v19 || v20 )
scripting_raise_exception(v19, v20);
return v5;
}
主要是用switch(Allocator)来选择内存类型,Allocator类型如下
public enum Allocator
{
Invalid = 0,
None = 1,
Temp = 2,
TempJob = 3,
Persistent = 4,
AudioKernel = 5,
}
与上面的分配内存方式对应起来了.
ManagedTempMemScope::Allocate
malloc_internal
这些都是Unity Native Momery内核分配函数,具体可以去看浅谈Unity内存管理里面有个初步认识.
总结
为什么Unity要使用NativeArray而不是List,应该有以下几点好处
- 不需要GC,速度快,生成和销毁代价低,适合作为ECS中的临时数据结构.
- Native可以直接调用,没有中间商赚差价.
坏处么
- 功能少,增加学习成本吧