无GC提取List<T>对应Array方法分析
Posted 程序员茶馆
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了无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行代码解注释后,测试结果如下图:
以上是关于无GC提取List<T>对应Array方法分析的主要内容,如果未能解决你的问题,请参考以下文章