unity nugi怎么实现unity3d 圆形进度条条

作为Unity3D的脚本而言,c#中for是否真的比foreach效率更高?
网上会流传一些说法是说在c#中for比foreach指令更加精简,效率更高,而且foreach会在每次循环的时候产生几kb的gc。所以在开发中(Unity)尽量使用for来替代foreach。请原谅我可耻的做个伸手党,请问这个结论在现在依然有效吗? 难道Mono或者微软开发c#的工程师不会对其进行优化吗?还是说这个是一个无法解决的问题?
## Update unity5.3.5p8出了个升级mono编译器的版本,消除了这部分GC,具体可参考:感谢
同学评论中的信息。就像下文中分析的,这就是Unity目前采用的C# Compiler的一个bug,所以解决方法完全可以通过只升级C# Compiler、而不用设计运行时更新来解决。实际上社区内已经有用户做了这部分尝试并且很成功,甚至可以支持到C# 6的大部分特性(比如async!),比Unity释出的当前这个版本还要激进。注意无论是Unity的这个版本,还是社区用户自己撸的这套,都还是实验性质。不过绝对是社区内期待已久的一大进步!==================================## Update 关于这个问题,另一篇英文的blog讲得更加深入,推荐给感兴趣的同学:这篇blog里,对于产生额外gc的原因分析,和我原答案是基本一致的。有趣的点是:blog的作者进一步深挖,找到了(曾经在微软开发C#编译器的大神)关于这个问题的一个回应:,结论是:(消除额外gc的优化方式)实际上严格来说是违法C#语言规范的(actually strictly speaking a violation of the C# specification)所以,其实mono也是被坑的 lol==================================“foreach会造成额外的gc开销”这个坑,在Unity社区里已经是个常识。避免这个坑的方式,是尽量改写为 等价于foreach的for/while代码 来避免额外的gc开销。关于这个坑的深层原因,在网上很早就有详尽的分析,比如: 中提到的“Should you avoid foreach loops?”然而关于这个现象的解释,还是有很多“玄学”在到处飞扬,包括题主提到的“for比foreach指令更加精简,更加高效”等等,终于也在知乎看到了。。。其实,真相当然只有一个:这就是个bug!以下是我重复了上面那个引文中关于这个bug的分析步骤,同时也是为了验证一下这一bug是否已经在Unity 5以及il2cpp中解决。=====================首先是Unity 5.0.2中写的一段测试代码:(_tl是一个List&int&)测试的比较简陋,就是在Update中每次调用不同的Test函数,在Unity的profiler中看一下:TestListForeach()TestListNoForeach()所以简单的测试已经有结论了:foreach会导致额外的gc对象,每次40B这个bug在Unity 5(使用mono的情况下)依然存在=====================那么额外的gc对象是什么呢?肯定是foreach被展开成了什么。。。那就把Unity编出来的Assembly-CSharp.dll(Unity工程目录下Library\ScriptAssemblies中)拉到任意一个反编译工具中看一下呗恩,foreach这个语法糖被正确展开成while了。这都是符合.Net常识的。这里再提一下
答案中提到的GetEnumerator的黑历史。这部分历史是真实存在的,GetEnumerator现在是返回struct了。然而问题并不出在这里,否则没法解释我们仅仅通过将foreach改写为while,就能消除多余的gc对象。那还有可能出问题的呢?当然只剩下那个using!这里需要再深入一层,于是我们来看一下生成的最终IL代码:真相就已经出现了:在finally里,mono编译出来的代码中有一次将valuetype的Enumerator,boxing的过程!!"What a waste!!!"这就是Unity中所带的老版本mono编译器的一个bug!!!=====================关于il2cpp的结果:可以看到这里il2cpp是忠实的做了翻译工作,连Box也照样保留了下来,想来想通过il2cpp来解决这个问题也是不现实的了。
先说结论,在Unity 4.x的语境下这个观点是正确的,能用for就别用foreach。背景:foreach会在托管堆上分配内存的问题在早期的C#中也是存在的,原因是foreach会将迭代器转换为IEnumerator。如果迭代器是引用类型,自然会分配在托管堆上;如果是值类型,值类型转换到接口类型是要装箱(boxing)的,需要在托管堆上分配内存并将数据拷贝过去。横竖都躲不过。后来微软在编译器中把这个问题优化掉了,办法是编译时查找名字叫做GetEnumerator的方法,如果提供了一个强类型的迭代器,生成的IL代码就会调用这个版本的GetEnumerator,强类型自然就没有GC的问题了。所以现在的C#里用foreach是没问题的,但是自己实现集合类型的时候记得同时实现一个强类型的IEnumerator&T&给编译器留个后门。而优化代码一定要在实际环境中测量数据。Unity的问题在于它用的是Mono 2.6,这个版本的Mono编译器还没有做这个优化。Unity的GC性能跟CLR的GC相比差很多,iOS上连JIT都没有,所以这方面还是比较敏感的。优化方法:能用for就不用foreach。把代码用Visual Studio编译成DLL。用不了for就手动调用强类型版本的GetEnumerator,然后自己写while (e.MoveNext()) ...,最后别忘记调迭代器的Dispose。笨是笨了点,但是在Update等每帧都需要执行的关键代码中可以减少大量GC Alloc,明显改善性能。偶尔才跑一次的话,迭代器一共也分配不了多少内存,Unity GC的Heap Block Size是1KB,能见缝插针的概率还是蛮大的。最后,官方表示Unity 5.x会修复这个问题。(感谢
题主这个问题是U3D的问题而不是C#的问题好吧
这个实现层面上的东西只能以实测为准。尤其是Unity这种纯粹把C#当脚本语言来用的场景和用C#开发的项目完全不是一回事儿。另外,尽量使用for来代替foreach肯定是错的,因为有些容器说不定按照索引来访问要比枚举慢得多。当然话说回来,这些容器本来就不应当提供按照索引检索的接口。顺便说一下,你贴的第二个链接说的显然是错的,那两个循环根本不等价,因为foreach是要把值取出来的,而for循环里面根本就没有取值,,,还因为是ArrayList导致平白无故多了一堆拆箱的操作。当然这和GC也没啥关系,只是说这种光靠拍脑袋不了解原理的测试毫无意义。
在普通CLR里,针对List&T&和Array的for比foreach快,而JIT在确定这个对象是List或Array的时候,会为foreach生成优化的代码,注意这里不仅仅是优化成for,而是把List直接优化成对其内部数组的访问,且不会有多余的边界检查,因此可以说List&T&是一个特殊的存在。这方面x86的JIT是这样的,x64的话,经tracing似乎还是foreach。x64运行时目前做的比较草率,似乎4.6开始(RyuJIT?)就精细很多了,因为之前不是特地写的JIT,而是用C++后端凑合改的。至于foreach的内存问题,首先,无论如何也不会多用几kb的内存,几十个字节最多了。其次,你看内置的集合,都额外写了一个struct的Enumerator,这样就可以把内存分配在栈而不是堆上,避免给GC造成压力(包括不用LINQ)。一般来说,对于需要密集foreach的情况,都是建议这么做的。至于Unity,估计对性能要求会比较高,所以更需要注意这方面。
C#大部分能循环的东西都没办法for,只能foreach,所以你这个问题其实没什么意义。如果针对的是数组或列表这些标准库类型的话,我认为编译器应该让for和foreach生成相同的代码。
性能什么的,应该不用考虑吧,等到慢了再说。
其实现在已经懒到Foreach都不写了List&T&.ForEach(item=&{ 操作});linq大法好 扩展方法大法好啊
我有看到一些网页上有unity的mono foreach泄露的问题,包括在4.x的issue list上有看到过。在unite 2015北京站,我有问过unity中国的人,他们并不知道这个问题。补充:unite2016上海站,承认这个问题了例如针对Dictionary&string, int& TestContainer进行测试:产生额外GC Alloc的原因:void TestListForeach()
foreach (var i in TestContainer)
CountString(i.Key);
会产生额外的GC Alloc,原因是这段代码等同于:void TestListNoForeachWithUsing()
using(var i = TestContainer.GetEnumerator())
while(i.MoveNext())
CountString(i.Current.Key);
当using结束进行Dispose时,因为IEnumerator&T&是结构体,进行了一次装箱操作。不产生额外GC Alloc的等价写法:void TestListNoForeachSafe()
var i = TestContainer.GetEnumerator();
while(i.MoveNext())
CountString(i.Current.Key);
i.Dispose();
这个写法写起来挺拗口的,如果不调用IEnumerator&T&.Dispose()会好很多:void TestListNoForeach()
var i = TestContainer.GetEnumerator();
while(i.MoveNext())
CountString(i.Current.Key);
那Dictionary.Dispose做了什么呢?
// 摘自Unity Mono的 mcs/class/corlib/System.Collections.Generic/Dictionary.cs 文件
public struct Enumerator : IEnumerator &T&, IDisposable {
Dictionary&TKey, TValue&
public void Dispose ()
dictionary =
通过以上代码,我认为针对Dictionary是可以不调用Dispose的。当然作为通用流程还是要调用的,如果想采用简单写法,必须明白Dispose做了什么。结论因为foreach比其替代写法,清晰明了太多,建议只用在一些不常调用的函数上。 可以先使用foreach开发,针对Profiler进行定点的优化, 为了项目不易出错,改写的代码,采用TestListNoForeachSafe写法。
foreach有隐含的对象引用问题,如果能替换的话,还是for比较放心。
已有帐号?
无法登录?
社交帐号登录

我要回帖

更多关于 unity加载场景进度条 的文章

 

随机推荐