C# for、while和foreach遍历产生GC

发表于2018-09-02
评论0 4.1k浏览
“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

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引