无GC提取List<T>对应Array方法分析

        最近在分析unity Mesh的一个方法 public void SetIndices(List<int> indices...)发现一个有趣的地方,其内部使用了一个叫NoAllocHelpers.ExtractArrayFromListT(list)的方法来提取与list对应的Array,该方法不会触发内存分配,进而无GC,而我们常用的ToArray()方法是会重新分配内存的。于是乎对其原理很好奇。

        我们知道List<T>内部维护一个叫T[] _temp的私有字段,我们在列表中的所有数据都存储在该数组的,盲猜上述方法是获取了_temp字段。有两种方式可以验证,第一种是修改上述方法所返回的数组,然后看list中的值是否改变;第二种是使用反射获取_temp字段,并检测上述方法返回数组的地址和_temp的地址是否一致。测试代码如下:

static unsafe void List2ArrayTest()
    {
        var list = new List<int>(6) { 1, 2, 3, 4, 5, 6 };
        var listStr = Concat(list);
        Debug.Log($"Original List:{listStr}");

        var type = Assembly.Load("UnityEngine").GetType("UnityEngine.NoAllocHelpers");
        var method = type.GetMethod("ExtractArrayFromListT", BindingFlags.Static | BindingFlags.Public);
        var generic = method.MakeGenericMethod(typeof(int));
        var result = generic.Invoke(null, new[] { list });
        var array = result as int[];

        //list.Add(7);//Resize
        for (var index = 0; index < array.Length; index++)
        {
            array[index] = array.Length - index;
        }
        listStr = Concat(array);
        Debug.Log($"No-Alloc array:{listStr}");
        listStr = Concat(list);
        Debug.Log($"Original List(Resize):{listStr}");

        var _temp = typeof(List<int>).GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(list) as int[];
        fixed (int* p = &array[0])
        {
            fixed (int* p2 = &_temp[0])
            {
                Debug.Log($"p=0x{((int)p):X2},p2=0x{((int)p2):X2}");
            }
        }
    }
    private static string Concat(IList list)
    {
        var sb = new StringBuilder();
        for (var index = 0; index < list.Count; index++)
        {
            sb.Append(list[index]);
        }
        return sb.ToString();
    }

        测试结果正如我们所料,如下图:

        但我们需要注意的是List扩容会导致内部_temp重新分配,这样的话不管是我们在扩容前使用上述方法或者是反射的方法获取到内部的数组均指向扩容前的内部数组,对其的修改就不会再影响到List了。将第13行代码解注释后,测试结果如下图:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值