C# 每一次迭代“;foreach&x201D;循环生成24字节的垃圾内存?

C# 每一次迭代“;foreach&x201D;循环生成24字节的垃圾内存?,c#,unity3d,foreach,mono,garbage-collection,C#,Unity3d,Foreach,Mono,Garbage Collection,我的朋友在C#上与Unity3D合作。他特别告诉我: 每个“foreach”循环的每次迭代都会产生24字节的垃圾 记忆 我也看到了这些信息 但这是真的吗 与我的问题最相关的一段: 将“foreach”循环替换为简单的“for”循环。出于某种原因, 每个“foreach”循环的每次迭代都会产生24字节的垃圾 记忆。一个简单的循环迭代10次,留下240字节的内存 准备好收集,这是不可接受的 foreach是一头有趣的野兽;很多人错误地认为它与IEnumerable[]/IEnumerator[]有关

我的朋友在C#上与Unity3D合作。他特别告诉我:

每个“foreach”循环的每次迭代都会产生24字节的垃圾 记忆

我也看到了这些信息

但这是真的吗

与我的问题最相关的一段:

将“foreach”循环替换为简单的“for”循环。出于某种原因, 每个“foreach”循环的每次迭代都会产生24字节的垃圾 记忆。一个简单的循环迭代10次,留下240字节的内存 准备好收集,这是不可接受的


foreach
是一头有趣的野兽;很多人错误地认为它与
IEnumerable[]
/
IEnumerator[]
有关,但事实并非如此。因此,说:

每个“foreach”循环的每次迭代都会生成24字节的垃圾内存

特别是,这完全取决于你在循环什么。例如,许多类型都有自定义迭代器-
List
,例如,有一个基于
struct
的迭代器。以下内容分配了0个对象:

// assume someList is a List<SomeType>
foreach(var item in someList) {
   // do something with item
}
//假设someList是一个列表
foreach(someList中的变量项){
//对这个项目做些什么
}
但是,此选项不执行对象分配:

IList<SomeType> data = someList;
foreach(var item in data) {
   // do something with item
}
IList data=someList;
foreach(数据中的var项){
//对这个项目做些什么
}
它看起来相同,但不是-通过将
foreach
更改为迭代
IList
(它没有自定义的
GetEnumerator()
方法,但实现了
IEnumerable
),我们强制它使用
IEnumerable[]
/
IEnumerator
API,必然会涉及到一个物体

编辑警告:注意,我在这里假设unity可以保留自定义迭代器的值类型语义;如果unity被迫将结构提升为对象,那么坦率地说,所有的赌注都没有了


碰巧,如果每个分配都很重要,我很乐意说“当然,在这种情况下使用
for
和索引器”,但从根本上说:
foreach
上的一个笼统的声明是没有帮助和误导的。

关于foreach,这里有一些很好的建议,但我必须插话马克上面关于标签的评论(不幸的是,我没有足够的代表在他的评论下面发表评论),他说


事实上,我不得不对那篇文章持保留态度,因为它 声明“调用对象上的标记属性分配和复制 附加内存”-此处的标记是字符串-抱歉,但是 这根本不是真的——所发生的只是引用 现有字符串对象复制到堆栈上-没有额外的 此处为对象分配

实际上,调用tag属性确实会产生垃圾。我猜想,在内部,标记存储为整数(或其他简单类型),当调用tag属性时,Unity使用内部表将int转换为字符串

它违背了原因,因为tag属性可以同样轻松地从该内部表返回字符串,而不是创建新字符串,但不管出于什么原因,它都是这样工作的

您可以使用以下简单组件自己进行测试:

public class TagGarbageTest : MonoBehaviour
{
    public int iterations = 1000;
    string s;
    void Start()
    {
        for (int i = 0; i < iterations; i++)
            s = gameObject.tag;
    }
}
公共类TagGarbageTest:monobhavior
{
公共整数迭代次数=1000;
字符串s;
void Start()
{
对于(int i=0;i
如果gameObject.tag没有产生垃圾,那么增加/减少迭代次数对GC分配没有影响(在Unity Profiler中)。事实上,随着迭代次数的增加,GC分配呈线性增加


还值得注意的是,name属性的行为方式完全相同(同样,这也是我无法理解的原因)。

您的朋友是正确的,从版本4.3.4开始,以下代码将在Unity中每帧生成24k垃圾:

using UnityEngine;
using System.Collections.Generic;

public class ForeachTest : MonoBehaviour 
{
    private string _testString = "this is a test";
    private List<string> _testList;
    private const int _numIterations = 10000;

    void Start()
    {
        _testList = new List<string>();
        for(int i = 0; i < _numIterations; ++i)
        {
            _testList.Add(_testString);
        }
    }

    void Update()
    {
        ForeachIter();
    }

    private void ForeachIter()
    {
        string s;

        for(int i = 0; i < 1000; ++i)
        {
            foreach(string str in _testList)
            {
                s = str;
            }
        }
    }
}
使用UnityEngine;
使用System.Collections.Generic;
公共类ForeachTest:单一行为
{
私有字符串_testString=“这是一个测试”;
私有列表(testList),;
私人建筑面积=10000;
void Start()
{
_testList=新列表();
对于(int i=0;i<\u numIterations;++i)
{
_添加(_testString);
}
}
无效更新()
{
前肢();
}
私有无效ForeachIter()
{
字符串s;
对于(int i=0;i<1000;++i)
{
foreach(testList中的字符串str)
{
s=str;
}
}
}
}
这显示了Unity profiler中的分配:


根据下面的链接,Unity使用的mono版本中的一个bug迫使结构枚举器被装箱,这似乎是一个合理的解释,尽管我还没有通过代码检查进行验证:

这太笼统了,不值得认真对待。不同的集合对
fo的行为方式不同reach
-作者没有指定他在说什么。(特别是,对于数组,编译器将有效地生成与
for
循环等价的内容。)是的,我也这么想。“foreach”是否会减慢“for”的速度?这完全取决于具体的场景。在某些情况下,对这方面的任何概括都可能是不正确的。整篇文章将被解读为“这在我们的具体情况下对我们有效”事实上,我不得不对那篇文章持保留态度,因为它声称“调用对象上的tag属性会分配和复制额外的内存”-其中
tag
这里有一个
string
-抱歉,但这根本不是真的-所发生的只是对existi的引用