Unity3D C# 中foreach的GC产出(2023年带数据)

这篇具有很好参考价值的文章主要介绍了Unity3D C# 中foreach的GC产出(2023年带数据)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

注意:笔者有点被杠怕了…确实也不严谨,也怕看不到,所以开头这里加一句:foreach本身不会产生GC,产生GC的原因是foreach使用了迭代器Enumerator,而取决于容器的不同,有些迭代器的初始化会产生GCAlloc…

一、Foreach究竟会不会产生GC?

很多读者在听一些群内大佬谈话过程中可能会听说foreach遍历集合会产生GC,笔者也是这么了解的,所以很多读者可能会和笔者一样在网上看到各种说法,将信将疑。
主要分为这几个立场:

1.foreach 会产生GC,在unity里别用,Mono的问题
2.foreach产生GC是被遍历的集合有问题,实现的不好,不是foreach的锅
3.foreach的GC问题已经修复了,大家可以毫不顾忌的使用

笔者在搜索了资料的基础上自己亲手实验,试图证明这些结论哪个是正确的,得到的结论是

网上的其他回答太过远古,甚至存在莫名的歧视foreach
有时候会产生一点点GC,但无需否定,甚至在现在可以忽略不计

如果不想看实验过程,可以直接翻到文末有结论!!!!

二、实验过程

首先我们以最常用的Dictionary进行讨论,因为我们经常使用foreach便捷的遍历Dictionary,难以用for进行

1. foreach遍历字典是否存在GC

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
public class MyGCTest : MonoBehaviour
{
    Dictionary<int,int> dic = new Dictionary<int, int>()
    {
        { 0, 0 },
        { 1, 1 }
    };
    void Update()
    {
        Profiler.BeginSample("ForeachGC");
        foreach (var x in dic){}
        Profiler.EndSample();
    }
}

foreach gc,Unity3D C# 技术探索,c#,unity,开发语言
foreach gc,Unity3D C# 技术探索,c#,unity,开发语言

答案显然是存在的但是笔者在不经意间发现,写在Update的foreach,居然仅仅在第一次调用时产生GC,以后的循环的foreach均不产生GC!!

2. foreach遍历字典在什么时候产生GC Alloc

根据上一步,笔者产生了以下猜想

1.字典内增加一个元素,foreach是否会再次产生GC
2.如果我分别遍历多个字典,会不会产生双份的GC

3.如果我遍历几个不同类型的字典呢?

根据以下代码验证,笔者在两个文件中分别监测GC的产生

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;


public class MyGCTest : MonoBehaviour
{
	//先整两个不同的字典
    Dictionary<int, int> dic = new Dictionary<int, int>()
    {
        { 0, 0 }
    };
    Dictionary<int, int> dic2 = new Dictionary<int, int>()
    {
        { 0, 0 },
        { 100,100}
    };
    void Update()
    {
        Profiler.BeginSample("ForeachGC");
        //先遍历一次第一个字典试试
        foreach (var x in dic){}
        //新增一个元素再试试
        if (!dic.ContainsKey(1)) dic.Add(1, 0);
        foreach (var x in dic){}
        //遍历第二个字典
        foreach (var x in dic2) {}
        Profiler.EndSample();
        //此时发现第一帧有0B 的GCAlloc
    }
}
public class MyGCtest2 : MonoBehaviour
{
    Dictionary<int, int> dic2 = new Dictionary<int, int>()
    {
        { 0, 0 },
        { 100,100}
    };
    void Update()
    {
        Profiler.BeginSample("ForeachGC3");
        foreach (var x in dic2) {}
        Profiler.EndSample();
        //第一帧产生96B GCAlloc
    }
}

foreach gc,Unity3D C# 技术探索,c#,unity,开发语言
foreach gc,Unity3D C# 技术探索,c#,unity,开发语言
这样我们就能得出

1.无论遍历几个字典,遍历几次,元素是否改变,都只产生96B的GCAlloc
2.foreach遍历字典的GCAlloc 全局仅产生一次,与所在文件,方法,类都无关

但是我们接下来想试试不同类型的字典…

Dictionary<int, int> dic2 = new Dictionary<int, int>()
    {
        { 0, 0 },
        { 100,100}
    };

    Dictionary<int, float> dic1 = new Dictionary<int, float>()
    {
        { 0, 0.2f },
        { 100,100.0f}
    };
    void Update()
    {
        Profiler.BeginSample("ForeachGC");
        foreach(var x in dic2)
        {

        }
        foreach (var x in dic1)
        {

        }
        Profiler.EndSample();
    }

foreach gc,Unity3D C# 技术探索,c#,unity,开发语言
GC突然变为192B ,是原来的二倍,显然每个类型的字典都会产生96B

1.无论遍历几个字典,遍历几次,元素是否改变,都只产生96B的GCAlloc
2.foreach遍历字典的GCAlloc 全局仅产生一次,与所在文件,方法,类都无关
3. foreach遍历字典产生GC与字典类型有关

3.foreach 遍历其他Collection呢?

	List<int> list = new List<int>() { 0,1,0};
    int[] arr= new int[3] { 0,1,0};
    void Update()
    {
        Profiler.BeginSample("ForeachGC");
        foreach(var x in list)
        {

        }
        foreach(var x in arr) { }
        Profiler.EndSample();
    }

foreach gc,Unity3D C# 技术探索,c#,unity,开发语言

甚至第一次GC都没产生,接下来我们仔细发掘一下原理。

三、foreach 为什么在遍历Dictionary时产生GC

foreach本质是对GetEnumerator(),MoveNext()等方法的简化,我们对IEnumerable等接口再熟悉不过了。

1.无论遍历几个字典,遍历几次,元素是否改变,都只产生96B的GCAlloc
2.foreach遍历字典的GCAlloc 全局仅产生一次,与所在文件,方法,类都无关
3. foreach遍历字典产生GC与字典类型有关

产生这些结论,得出foreach的CG产出和字典类型相关,而与其他的因素无关的结论。我能猜测出GetEnumerator始终返回的是Enumerator的单例,每个字典类型都包含一个实例,所以形成每个字典类型都产生一定GC的现象。

foreach gc,Unity3D C# 技术探索,c#,unity,开发语言
foreach gc,Unity3D C# 技术探索,c#,unity,开发语言
我们详细展开分析,发现在GetEnumerator处产生96B,在MoveNext处产生96B
甚至笔者为了探究这一内容,写了第三个Dictionary,发现GetEnumerator处产生144B,在MoveNext处产生144B
笔者得到了以下结论

1.每个类型Dictionary<T,K>首次foreach均产生96B 的GCAlloc
2.每个96B的GCAlloc分别为 48B的GetEnumerator()和 48B的MoveNext()

3.Dictionary的迭代方式类似于单例,每个类型全局仅加载一次

四、结论

  1. foreach在遍历System.Collections.Generic内的集合时不会无理由产生不可接受的GC
    事实上,遍历List和数组时不会创建Enumerator,即一直保持0GC
  2. foreach在遍历字典时,仅对每个类型字典在首次调用时产生一次GC,以后同类型字典不会再产生GC,与其他因素无关。
    也就是说,你只需要对Dictionary<int,int>使用过foreach,以后再使用同类型的字典foreach就不会产生GC,无论是否为同一实例,元素是否变化,文件是否相同,方法和类是否相同。
  3. 对字典Values/Keys单独foreach,将产生更多的GC,大概多24B,与上面提到的相近,其他一致。

下面是一些评论的回复,笔者重测的结果…

Re1: 和foreach有关系吗?其实没有,但是往往有关联,重点是迭代器实现的问题,foreach对C#封装的原生容器都没太大问题,但是原生里有几个有GC,小心用户自己封装的可迭代容器即可。

Re2: 部分读者认为结论3测错,在此进行统一回复

直接对Values进行迭代
foreach gc,Unity3D C# 技术探索,c#,unity,开发语言
foreach gc,Unity3D C# 技术探索,c#,unity,开发语言
直接对字典迭代
foreach gc,Unity3D C# 技术探索,c#,unity,开发语言
foreach gc,Unity3D C# 技术探索,c#,unity,开发语言

先迭代字典再迭代Values
foreach gc,Unity3D C# 技术探索,c#,unity,开发语言

foreach gc,Unity3D C# 技术探索,c#,unity,开发语言文章来源地址https://www.toymoban.com/news/detail-586275.html

到了这里,关于Unity3D C# 中foreach的GC产出(2023年带数据)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包