【Dots之002】 List<T>无GC转换为T[] NativeArry<T>无GC转换T[]

一、背景

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的肯定是没有坏处的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值