本文无意比较for和foreach谁效率更高,不会设计到for和foreach取值之类的等等。单纯探讨foreach会不会影响unity3d效率。
事情开端是这样的,之前在看unity优化的时候,遇见了这么一句:
尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。
由于刚接触unity以及c#不久,当时没有仔细研究内在原理,只是简单相信了这个说法。于是接下来我会在代码中尽量避免使用foreach,虽然我觉得foreach真的挺好用的。
然后,现在每次遇见遍历,我都会思考为什么foreach会有这样的行为,我想知道真正的原因,直到今天,我才深入寻找资料去论证这个观点。
关于这个说法,百度了下(懒得翻墙去google),找到一篇被广泛转发的文章:
里面代码如下:
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回收机制里回收时间点不固定,如果隔一个较长时间点统一回收,必然会导致顿卡现象出现。
回答问题之前,我打算顺着 王剑飞 先生的代码验证一次,代码跟他在知乎回答里的几乎一致:
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 initialization
    void Start () {
    }
    // Update is called once per frame
    void Update () {
        TestForeach();
        //TestNoForeach();
        //TestFor();
        //TestUsing();
    }
    void TestForeach()
    {
        //Debug.Log("TestForeach");
        foreach (var e in iList){
            //Debug.Log(e);
        }
        //foreach (var e in iList){
        //    foreach (var item 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了。
相信看完上面的朋友已经有了自己的想法了。
1.脚本直接放在Unity工程中,如果不是在Update中每帧调用,使用foreach没有太大顾虑。如果是Update中,而且是多个地方频繁使用foreach,就需要慎重考虑了。
2.脚本放入Dll中,爱咋咋地吧!
版权声明:本文为博主原创文章,未经博主允许不得转载。
Unity3D系列1 : foreach对于性能到底有没有影响
原文地址:http://blog.csdn.net/crazygougou/article/details/47450923