Unity3D系列1 : foreach对于性能到底有没有影响

0x00 序言

本文无意比较for和foreach谁效率更高,不会设计到for和foreach取值之类的等等。单纯探讨foreach会不会影响unity3d效率。

事情开端是这样的,之前在看unity优化的时候,遇见了这么一句:

尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。

由于刚接触unity以及c#不久,当时没有仔细研究内在原理,只是简单相信了这个说法。于是接下来我会在代码中尽量避免使用foreach,虽然我觉得foreach真的挺好用的。

然后,现在每次遇见遍历,我都会思考为什么foreach会有这样的行为,我想知道真正的原因,直到今天,我才深入寻找资料去论证这个观点。

0x01 c#中for比foreach指令更加精简,效率更高?

关于这个说法,百度了下(懒得翻墙去google),找到一篇被广泛转发的文章:

Unity里面应尽量避免使用foreach

里面代码如下:

using UnityEngine; using System.Collections;public class ForeachTest : MonoBehaviour {protected ArrayList m_array;void Start (){m_array = new ArrayList();for (int i = 0; i < 2; i++)m_array.Add(i);}void Update (){for (int i = 0; i < 1000; i++){foreach (int e in m_array){//big gc alloc!!! do not use this code! }}for (int i = 0; i < 1000; i++){for (int k = 0; k < m_array.Count; k++){//no gc alloc!! }}} }

初步看到代码后,很是疑惑,为什么一个简单的foreach会有这么多的内存开销。带着不解,继续搜索,直到看到知乎上这么一篇回答:

作为Unity3D的脚本而言,c#中for是否真的比foreach效率更高?

所以的疑惑在这里几乎都可以得到解答:

每一个foreach都会产生40B的内存,如果是在某个脚本的Update中使用foreach,那么每帧都会有40B的GC ALLOC。

之前在另一个地方看见过2014Unity亚洲开发者大会会议简录上有一个说法:

检测每帧都具有20B以上内存分配的选项

这几乎意味着在Update中使用foreach是不太明智的选择。关于为什么每帧20B以上内存分配不太好,我还没有仔细研究,个人猜测如果每帧20B,累计一段时间会有大量回收内存堆积,而mono回收机制里回收时间点不固定,如果隔一个较长时间点统一回收,必然会导致顿卡现象出现。

0x02 我们就不使用foreach了吗?

回答问题之前,我打算顺着 王剑飞 先生的代码验证一次,代码跟他在知乎回答里的几乎一致:

using UnityEngine;using System.Collections;using System.Collections.Generic;public class GCTest : MonoBehaviour {List<int> iList = new List<int>();int count = 10;void Awake(){for (int i = 0; i < count; i++){iList.Add(i);}}// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {TestForeach();}void TestForeach(){//Debug.Log(“TestForeach”);foreach (var e in iList){//Debug.Log(e);}}void TestNoForeach(){//Debug.Log(“TestNoForeach”);var e = iList.GetEnumerator();while (e.MoveNext()){//Debug.Log(e.Current);}}void TestFor(){//Debug.Log(“TestFor”);for (int i = 0; i < count; ++i){//Debug.Log(iList[i]);}}void TestUsing(){//Debug.Log(“TestUsing”);using(var e = iList.GetEnumerator()){while (e.MoveNext()){//Debug.Log(e.Current);}};}}

请注意,这是直接在unity工程里面添加了C#脚本,使用内置的mono编译器来编译代码。

当我在Update中使用TestForeach()时,结果如下:

可以看见在我Win7 + Unity64 环境下,foreach的确有40B的GC Alloc。

接着TestForeach()中使用注释代码:

foreach (var e in iList){foreach (var item in iList){//Debug.Log(e);}}

一共440B内存开销,这侧面印证了一个foreach会产生40B的堆内存说法。

接着在Update中使用TestNoForeach(),结果如下:

可以看到没有GC Alloc。

这样的结果似乎还是在说,不要使用foreach啊!

我又回想起来知乎上还说过:

**真相就已经出现了: 在finally里,mono编译出来的代码中有一次将valuetype的Enumerator,boxing的过程!! What a waste!!! 这就是Unity中所带的老版本mono编译器的一个bug!**

既然是mono老版本的bug,能绕过去吗?

答案是肯定的,因为我们的项目正好是把脚本达成dll包,放入工程使用。编译脚本时,使用的是MS最新的编译器,这样是不是没问题呢?

测试一下,将新编译的dll放入工程,结果如下:

同样使用的是TestForeach()方法,不过这次已经没有GC Alloc了。

0x03 结论?

相信看完上面的朋友已经有了自己的想法了。

1.脚本直接放在Unity工程中,如果不是在Update中每帧调用,使用foreach没有太大顾虑。如果是Update中,而且是多个地方频繁使用foreach,就需要慎重考虑了。

2.脚本放入Dll中,,爱咋咋地吧!

不要做刺猬能不与人结仇就不与人结仇,

Unity3D系列1 : foreach对于性能到底有没有影响

相关文章:

你感兴趣的文章:

标签云: