我的朋友在C#上使用Unity3D。他特意告诉我:
每个“foreach”循环的每次迭代都会产生24字节的垃圾 存储器中。
我也看到了这些信息here
但这是真的吗?
与我的问题最相关的段落:
用简单的“for”循环替换“foreach”循环。由于某些原因, 每个“foreach”循环的每次迭代都会产生24字节的垃圾 记忆。一个简单的循环迭代10次,留下240字节的内存 准备收集,这是不可接受的
答案 0 :(得分:12)
foreach
是一个有趣的野兽;很多人错误地认为它与IEnumerable[<T>]
/ IEnumerator[<T>]
有关,但事实并非如此。因此,毫无意义地说:
每个“foreach”循环的每次迭代都会生成24字节的垃圾内存。
特别是,它取决于你正在循环的内容。例如,很多类型都有自定义迭代器 - 例如List<T>
- 有一个基于struct
的迭代器。以下分配零对象:
// assume someList is a List<SomeType>
foreach(var item in someList) {
// do something with item
}
但是,这个会进行对象分配:
IList<SomeType> data = someList;
foreach(var item in data) {
// do something with item
}
看起来相同但不是 - 通过更改foreach
来迭代IList<SomeType>
(没有自定义GetEnumerator()
方法,但实现{{1} })我们强制它使用IEnumerable<SomeType>
/ IEnumerable[<T>]
API,这很大程度上涉及一个对象。
编辑警告:注意我在这里假设unity可以保留自定义迭代器的值类型语义;如果团结被迫将结构提升为物体,那么坦率地说所有的赌注都会被取消。
碰巧,我很乐意说“确定,在这种情况下使用IEnumerator<T>
和索引器”如果每个分配都很重要,但从根本上说:for
上的一揽子声明是无益的,误导。
答案 1 :(得分:10)
您的朋友是正确的,从版本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;
}
}
}
}
这显示了Unity Profiler中的分配:
根据以下链接,这是Unity使用的mono版本中的一个错误,它强制结构枚举器被装箱,这似乎是一个合理的解释,虽然我没有通过代码检查验证:Blog post discussing the boxing bug
答案 2 :(得分:5)
关于foreach,这里有一些很好的建议,但我必须对Mark上面关于标签的评论表示不满(不幸的是,我没有足够的代表在他的评论下面发表评论),他说,
实际上,我必须用一小撮盐来拿这篇文章,因为它 声明“调用对象上的tag属性分配和复制 额外的内存“ - 这里的标签是一个字符串 - 抱歉,但是 根本不是真的 - 所有发生的事情都是对它的引用 现有的字符串对象被复制到堆栈上 - 没有额外的 对象分配在这里。 -
实际上,调用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;
}
}
如果gameObject.tag没有产生垃圾,那么增加/减少迭代次数对GC Allocation(在Unity Profiler中)没有影响。实际上,随着迭代次数的增加,我们会看到GC分配的线性增长。
值得注意的是,name属性的行为方式完全相同(再次,为什么我不知道)。