一、背景
1、 在主线程中有时候需要把List转换为T[],可能会用到 ToArray() 进行转换;如果数据量大,或者在update中频繁执行,那么带来的GC就比较麻烦了;
2、有时候需要NativeArry这种类型数据转换为List来用;比如在Job中计算Mesh的数据;在主线程中需要对通过T[]或者List来设置Mesh的相关数据,这时候也会产生GC;
二、分析
1、要想获取到List的无GC数组,这里可以借助与Unity本身的内部函数UnityEngine.NoAllocHelpersExtractArrayFromListT(),由于这个函数是没有开放的,所以我们可以通过反射,缓存起来使用;该部分内容通过定义一个静态类NoAllocHelpers来实现;
2、为了方便List调用方面,这里直接对它进行扩展,在静态类ExtendHelper中给它扩展一个方法NativeAddRange;这样调用起来就方便了
三、代码部分
1、ExtendHelper 扩展代码:
using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using UnityEngine.Profiling;
public static class ExtendHelper
{
/// <summary>
/// NativeArray<T>转换为List<T>
/// </summary>
/// <param name="list"></param>
/// <param name="nativeArray"></param>
/// <typeparam name="T"></typeparam>
public static unsafe void NativeAddRange<T>(this List<T> list, NativeArray<T> nativeArray)
where T : struct
{
NativeAddRange(list, nativeArray.GetUnsafePtr(), nativeArray.Length);
}
/// <summary>
/// DynamicBuffer<T>转换为List<T>
/// </summary>
/// <param name="list"></param>
/// <param name="dynamicBuffer"></param>
/// <typeparam name="T"></typeparam>
public static unsafe void NativeAddRange<T>(this List<T> list, DynamicBuffer<T> dynamicBuffer) where T : struct
{
NativeAddRange(list, dynamicBuffer.GetUnsafePtr(), dynamicBuffer.Length);
}
private static unsafe void NativeAddRange<T>(List<T> list, void* arrayBuffer, int length)
where T : struct
{
var index = list.Count;
var newLength = index + length;
// Resize our list if we require
if (list.Capacity < newLength)
{
list.Capacity = newLength;
}
var items = NoAllocHelpers.ExtractArrayFromListT(list);
var size = UnsafeUtility.SizeOf<T>();
// Get the pointer to the end of the list
var bufferStart = (IntPtr) UnsafeUtility.AddressOf(ref items[0]);
var buffer = (byte*) (bufferStart + (size * index));
//数据拷贝
UnsafeUtility.MemCpy(buffer, arrayBuffer, length * (long) size);
//重新分配内存容量
NoAllocHelpers.ResizeList(list, newLength);
}
}
2、NoAllocHelpers部分
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using Object = UnityEngine.Object;
/// <summary>
/// Provides access to the internal UnityEngine.NoAllocHelpers methods.
/// </summary>
public static class NoAllocHelpers
{
private static readonly Dictionary<Type, Delegate>
ExtractArrayFromListTDelegates = new Dictionary<Type, Delegate>();
private static readonly Dictionary<Type, Delegate> ResizeListDelegates = new Dictionary<Type, Delegate>();
/// <summary>
/// Extract the internal array from a list.
/// </summary>
/// <typeparam name="T"><see cref="List{T}"/>.</typeparam>
/// <param name="list">The <see cref="List{T}"/> to extract from.</param>
/// <returns>The internal array of the list.</returns>
public static T[] ExtractArrayFromListT<T>(List<T> list)
{
if (!ExtractArrayFromListTDelegates.TryGetValue(typeof(T), out var obj))
{
var ass = Assembly.GetAssembly(typeof(Mesh)); // any class in UnityEngine
var type = ass.GetType("UnityEngine.NoAllocHelpers");
var methodInfo = type.GetMethod("ExtractArrayFromListT", BindingFlags.Static | BindingFlags.Public)
.MakeGenericMethod(typeof(T));
obj = ExtractArrayFromListTDelegates[typeof(T)] =
Delegate.CreateDelegate(typeof(Func<List<T>, T[]>), methodInfo);
}
var func = (Func<List<T>, T[]>) obj;
return func.Invoke(list);
}
/// <summary>
/// Resize a list.
/// </summary>
/// <typeparam name="T"><see cref="List{T}"/>.</typeparam>
/// <param name="list">The <see cref="List{T}"/> to resize.</param>
/// <param name="size">The new length of the <see cref="List{T}"/>.</param>
public static void ResizeList<T>(List<T> list, int size)
{
if (!ResizeListDelegates.TryGetValue(typeof(T), out var obj))
{
var ass = Assembly.GetAssembly(typeof(Mesh)); // any class in UnityEngine
var type = ass.GetType("UnityEngine.NoAllocHelpers");
var methodInfo = type.GetMethod("ResizeList", BindingFlags.Static | BindingFlags.Public)
.MakeGenericMethod(typeof(T));
obj = ResizeListDelegates[typeof(T)] =
Delegate.CreateDelegate(typeof(Action<List<T>, int>), methodInfo);
}
var action = (Action<List<T>, int>) obj;
action.Invoke(list, size);
}
}
3、使用范例
public void UpdateLogicForJob(NativeArray<Vector3> vertextPos, NativeArray<Vector2> uvs,
NativeArray<Color32> colors,
NativeArray<Vector2> offsets)
{
_vertices.NativeAddRange(vertextPos);
_uv.NativeAddRange(uvs);
_colors32.NativeAddRange(colors);
_offsets.NativeAddRange(offsets);
var vers = NoAllocHelpers.ExtractArrayFromListT(_vertices);
var uv1s = NoAllocHelpers.ExtractArrayFromListT(_uv);
var cols = NoAllocHelpers.ExtractArrayFromListT(_colors32);
var offs = NoAllocHelpers.ExtractArrayFromListT(_offsets);
}
这里就是对传入的数据进行无GC转换;
四、总计
针对于List这种操作频繁,或者数据量大的情况,采用上文的方式进行数据转换,能够得到一个比较好的性能效果;不过个人建议用这种没GC的肯定是没有坏处的。