C# for、while和foreach遍历产生GC
发表于2018-09-02
“foreach会造成额外的gc开销”这个坑,在Unity社区里已经是个常识。
避免这个坑的方式,是尽量改写为 等价于foreach的for/while代码 来避免额外的gc开销。
最近在网上看到了有关于Foreach遍历会产生GC的文章今天测试了下,在这里记录下怕忘记。
先上测试代码
using UnityEngine; using System.Collections; using System.Collections.Generic; public class People { public string Name; public int Age; } public class FontTextureSet : MonoBehaviour { int[] tempsInts = new[] { 1, 3, 4, 5, 6 }; private People[] peoples; private List<People> peoplesList; private List<int> intList; private Dictionary<int, People> peopleDictionary; private Dictionary<int, int> intDictionary; private int tem; // Use this for initialization void Awake() { peoples = new People[5]; peoplesList = new List<People>(); intList = new List<int>(); peopleDictionary = new Dictionary<int, People>(); intDictionary = new Dictionary<int, int>(); for (int i = 0; i < 5; i++) { People people = new People(); people.Age = i; people.Name = ((char) 'A' + i).ToString(); peoples[i] = people; peoplesList.Add(people); peopleDictionary.Add(i, people); intDictionary.Add(i,i); intList.Add(i); } } void Update() { Profiler.BeginSample("Foreach Int Array"); foreach (int v in tempsInts) { tem = v; } Profiler.EndSample(); Profiler.BeginSample("Foreach People Array"); foreach (People p in peoples) { } Profiler.EndSample(); Profiler.BeginSample("Foeach Int List"); foreach (int i in intList) { } Profiler.EndSample(); Profiler.BeginSample("Foreach People List"); foreach (People people in peoplesList) { } Profiler.EndSample(); Profiler.BeginSample("Foreach People Dictionary"); foreach (KeyValuePair<int, People> keyValuePair in peopleDictionary) { } Profiler.EndSample(); Profiler.BeginSample("Foeach peopleDictionary.Values"); foreach (People people in peopleDictionary.Values) { } Profiler.EndSample(); Profiler.BeginSample("Foreach Int Dictionary"); foreach (KeyValuePair<int, int> keyValuePair in intDictionary) { } Profiler.EndSample(); Profiler.BeginSample("Foreach intDictionary.Values"); foreach (int value in intDictionary.Values) { } Profiler.EndSample(); } }
ok,在来看看GC情况
哦 my god 这是真的吗,除了数组的遍历其他都产生了GC,这要是真的在Update里用了foreach遍历Directionary和List那不是要疯了,这代码要是让主程看到他肯定要哭,好吧,废话不多说,先来分析一下结果:
1、就是foreach变量数组不会产生GC,遍历Directionary和List都会产生GC
2、foreach遍历List产生GC是24B,Directionary是28B,注意Directionary遍历有多种这里我只测试了两种用foreach的遍历方法,有Directionary.Values遍历会产生40B的GC
3、GC的产生与数据类型无关,与数据结构有关
接下来能是for和while其实他们两差不多在遍历Directionary时都会产生GC,有一种情况除外,先来看测试代码
using UnityEngine; using System.Collections; using System.Collections.Generic; public class People { public string Name; public int Age; } public class FontTextureSet : MonoBehaviour { int[] tempsInts = new[] { 1, 3, 4, 5, 6 }; private People[] peoples; private List<People> peoplesList; private List<int> intList; private Dictionary<int, People> peopleDictionary; private Dictionary<int, int> intDictionary; private int tem; // Use this for initialization void Awake() { peoples = new People[5]; peoplesList = new List<People>(); intList = new List<int>(); peopleDictionary = new Dictionary<int, People>(); intDictionary = new Dictionary<int, int>(); for (int i = 0; i < 5; i++) { People people = new People(); people.Age = i; people.Name = ((char) 'A' + i).ToString(); peoples[i] = people; peoplesList.Add(people); peopleDictionary.Add(i, people); intDictionary.Add(i,i); intList.Add(i); } } void Update() { Profiler.BeginSample("For Int Array"); for (int i = 0; i < tempsInts.Length; i++) { //tempsInts[i]; } Profiler.EndSample(); Profiler.BeginSample("For People Array"); for (int i=0; i<peoples.Length; i++) { } Profiler.EndSample(); Profiler.BeginSample("For Int List"); for (int i=0; i< intList.Count; i++) { } Profiler.EndSample(); Profiler.BeginSample("For People List"); for (int i=0; i<peoplesList.Count; i++) { } Profiler.EndSample(); Profiler.BeginSample("For People Dictionary.Values"); for (int i =0;i<peopleDictionary.Values.Count; i++) { } Profiler.EndSample(); Profiler.BeginSample("For peopleDictionary"); IEnumerator e = peopleDictionary.GetEnumerator(); e.MoveNext(); for (int i=0; i<peopleDictionary.Count; e.MoveNext(), i++) { } Profiler.EndSample(); Profiler.BeginSample("While Dictionary"); IEnumerator enumerator = peopleDictionary.GetEnumerator(); //e.MoveNext(); while (e.MoveNext()) { } Profiler.EndSample(); Profiler.BeginSample("While List"); int num = 0; while (num < peoplesList.Count) { num++; } Profiler.EndSample(); } }
结果图如下:
结论:
1、Dictionary不管是for、while还是foreach都会产生GC都是28B(注意这里没有用var这个关键字,下面我会对用var的遍历做测试)
2、For遍历Dictionary.Values产生的GC是72B,比foreach多32B
3、For和while遍历数组和List都不会产生GC
上面我提到了var这个关键字,ok,下面我们就对这个做个测试
using UnityEngine; using System.Collections; using System.Collections.Generic; public class People { public string Name; public int Age; } public class FontTextureSet : MonoBehaviour { int[] tempsInts = new[] { 1, 3, 4, 5, 6 }; private People[] peoples; private List<People> peoplesList; private List<int> intList; private Dictionary<int, People> peopleDictionary; private Dictionary<int, int> intDictionary; private int tem; // Use this for initialization void Awake() { peoples = new People[5]; peoplesList = new List<People>(); intList = new List<int>(); peopleDictionary = new Dictionary<int, People>(); intDictionary = new Dictionary<int, int>(); for (int i = 0; i < 5; i++) { People people = new People(); people.Age = i; people.Name = ((char) 'A' + i).ToString(); peoples[i] = people; peoplesList.Add(people); peopleDictionary.Add(i, people); intDictionary.Add(i,i); intList.Add(i); } } void Update() { Profiler.BeginSample("Foreach Var Dictionary"); foreach (var VARIABLE in peopleDictionary) { } Profiler.EndSample(); Profiler.BeginSample("Foreach Var Dictionary.Values"); foreach (var value in peopleDictionary.Values) { } Profiler.EndSample(); Profiler.BeginSample("Foreach Var List"); foreach (var people in peoplesList) { } Profiler.EndSample(); Profiler.BeginSample("For Var Dictionary"); var e = peopleDictionary.GetEnumerator(); e.MoveNext(); for (int i=0; i<peopleDictionary.Count; e.MoveNext(), i++) { } Profiler.EndSample(); Profiler.BeginSample("While Var Dictionary"); var enumerator = peopleDictionary.GetEnumerator(); //e.MoveNext(); while (e.MoveNext()) { } Profiler.EndSample(); } }
结果如下图所示:
可以看出对于foreach来说没什么变化,但对for和while来说意义非凡啊,Directionary的遍历没有GC了。
OK,最后来做个总结:
1、遍历不要用foreach,全用for和while
2、遍历Dictionary是有一下两种方式的一种:
var e = peopleDictionary.GetEnumerator();
e.MoveNext();
for (int i=0; i<peopleDictionary.Count; e.MoveNext(), i++)
{
}
var enumerator = peopleDictionary.GetEnumerator();
//e.MoveNext();
while (e.MoveNext())
{
}
}
以上的测试只是简单测试不全,但已经达到目的了:就是以后遍历怎么遍历不会产生GC。
来自:https://blog.csdn.net/hyf2713/article/details/52335113