网上说unity3d不要用unity c foreachh,有内存分配和回收,到底有没有道理

转载总结使用Unity3D优化游戏运行性能的经验
流畅的游戏玩法来自流畅的帧率,而我们即将推出的动作平台游戏《Shadow Blade》已经将在标准iPhone和iPad设备上实现每秒60帧视为一个重要目标。
以下是我们在紧凑的优化过程中提升游戏运行性能,并实现目标帧率时需要考虑的事项。
当基本游戏功能到位时,就要确保游戏运行表现能够达标。我们衡量游戏运行表现的一个基本工具是Unity内置分析器以及Xcode分析工具。使用Unity分析器来分析设备上的运行代码真是一项宝贵的功能。
我们总结了这种为将目标设备的帧率控制在60fps而进行衡量、调整、再衡量过程的中相关经验。
一、遇到麻烦时要调用&垃圾回收器&(Garbage Collector,无用单元收集程序,以下简称GC)
由于具有C/C++游戏编程背景,我们并不习惯无用单元收集程序的特定行为。确保自动清理你不用的内存,这种做法在刚开始时很好,但很快你就公发现自己的分析器经常显示CPU负荷过大,原因是垃圾回收器正在收集垃圾内存。这对移动设备来说尤其是个大问题。要跟进内存分配,并尽量避免它们成为优先数,以下是我们应该采取的主要操作:
1.移除代码中的任何字符串连接,因为这会给GC留下大量垃圾。
2.用简单的&for&循环代替&foreach&循环。由于某些原因,每个&foreach&循环的每次迭代会生成24字节的垃圾内存。一个简单的循环迭代10次就可以留下240字节的垃圾内存。
3.更改我们检查游戏对象标签的方法。用&if (go.CompareTag (&Enemy&)&来代替&if (go.tag == &Enemy&)& 。在一个内部循环调用对象分配的标签属性以及拷贝额外内存,这是一个非常糟糕的做法。
4.对象库很棒,我们为所有动态游戏对象制作和使用库,这样在游戏运行时间内不会动态分配任何东西,不需要的时候所有东西反向循环到库中。
5.不使用LINQ命令,因为它们一般会分配中间缓器,而这很容易生成垃圾内存。
二、谨慎处理高级脚本和本地引擎C++代码之间的通信开销。
所有使用Unity3D编写的游戏玩法代码都是脚本代码,在我们的项目中是使用Mono执行时间处理的C#代码。任何与引擎数据的通信需求都要有一个进入高级脚本语言的本地引擎代码的调用。这当然会产生它自己的开销,而尽量减少游戏代码中的这些调用则要排在第二位。
1.在这一情景中四处移动对象要求来自脚本代码的调用进入引擎代码,这样我们就会在游戏玩法代码的一个帧中缓存某一对象的转换需求,并一次仅向引擎发送一个请求,以便减少调用开销。这种模式也适用于其他相似的地方,而不仅局限于移动和旋转对象。
2.将引用本地缓存到元件中会减少每次在一个游戏对象中使用 &GetComponent& 获取一个元件引用的需求,这是调用本地引擎代码的另一个例子。
三、物理效果
1.将物理模拟时间步设置到最小化状态。在我们的项目中就不可以将让它低于16毫秒。
2.减少角色控制器移动命令的调用。移动角色控制器会同步发生,每次调用都会耗损极大的性能。我们的做法是缓存每帧的移动请求,并且仅运用一次。
3.修改代码以免依赖&ControllerColliderHit& 回调函数。这证明这些回调函数处理得并不十分迅速。
4.面对性能更弱的设备,要用skinned mesh代替physics cloth。cloth参数在运行表现中发挥重要作用,如果你肯花些时间找到美学与运行表现之间的平衡点,就可以获得理想的结果。
5.在物理模拟过程中不要使用ragdolls,只有在必要时才让它生效。
6.要谨慎评估触发器的&onInside&回调函数,在我们的项目中,我们尽量在不依赖它们的情况下模拟逻辑。
7.使用层次而不是标签。我们可以轻松为对象分配层次和标签,并查询特定对象,但是涉及碰撞逻辑时,层次至少在运行表现上会更有明显优势。更快的物理计算和更少的无用分配内存是使用层次的基本原因。
8.千万不要使用Mesh对撞机。
9.最小化碰撞检测请求(例如ray casts和sphere checks),尽量从每次检查中获得更多信息。
四、让AI代码更迅速
我们使用AI敌人来阻拦忍者英雄,并同其过招。以下是与AI性能问题有关的一些建议:
1.AI逻辑(例如能见度检查等)会生成大量物理查询。可以让AI更新循环设置低于图像更新循环,以减少CPU负荷。
五、最佳性能表现根本就不是来自代码!
没有发生什么情况的时候,就说明性能良好。这是我们关闭一切不必要之物的基本原则。我们的项目是一个侧边横向卷轴动作游戏,所以如果不具有可视性时,就可以关闭许多动态关卡物体。
1.使用细节层次的定制关卡将远处的敌人AI关闭。
2.移动平台和障碍,当它们远去时其物理碰撞机也会关闭。
3.Unity内置的&动画挑选&系统可以用来关闭未被渲染对象的动画。
4.所有关卡内的粒子系统也可以使用同样的禁用机制。
六、回调函数!那么空白的回调函数呢?
要尽量减少Unity回调函数。即使敌人回调函数存在性能损失。没有必要将空白的回调函数留在代码库中(有时候介于大量代码重写和重构之间)。
七、让美术人员来救场
在程序员抓耳挠腮,绞尽脑汁去想该如何让每秒运行更多帧时,美术人员总能神奇地派上大用场。
1.共享游戏对象材料,令其在Unity中处于静止状态,可以让它们绑定在一起,由此产生的简化绘图调用是呈现良好移动运行性能的重要元素。
2.纹理地图集对UI元素来说尤其有用。
3.方形纹理以及两者功率的合理压缩是必不可少的步骤。
4.我们的美术人员移除了所有远处背景的网格,并将其转化为简单的2D位面。
5.光照图非常有价值。
6.我们的美术人员在一些关口移除了额外顶点。
7.使用合理的纹理mip标准是一个好主意(游戏邦注:要让不同分辨率的设备呈现良好的帧率时尤其如此)。
8.结合网格是美术人员可以发挥作用的另一个操作。
9.我们的动画师尽力让不同角色共享动画。
10.要找到美学/性能之间的平衡,就免不了许多粒子效果的迭代。减少发射器数量并尽量减少透明度需求也是一大挑战。
八、要减少内存使用
使用大内存当然会对性能产生负面影响,但在我们的项目中,我们的iPod由于超过内存上限而遭遇了多次崩溃事件。我们的游戏中最耗内存的是纹理。
1.不同设备要使用不同的纹理大小,尤其是UI和大型背景中的纹理。《Shadow Blade》使用的是通用型模板,但如果在启动时检测到设备大小和分辨率,就会载入不同资产。
2.我们要确保未使用的资产不会载入内存。我们必须迟一点在项目中找到仅被一个预制件实例引用,并且从未完全载入内存中实例化的资产。
3.去除网格中的额外多边形也能实现这一点。
4.我们应该重建一些资产的生周期管理。例如,调整主菜单资产的加载/卸载时间,或者关卡资产、游戏音乐的有效期限。
5.每个关卡都要有根据其动态对象需求而量身定制的特定对象库,并根据最小内存需求来优化。对象库可以灵活一点,在开发过程中包含大量对象,但知道游戏对象需求后就要具体一点。
6.保持声音文件在内存的压缩状态也是必要之举。
原文:/forum.php?mod=viewthread&tid=201&extra=page%3D3
> 本站内容系网友提交或本网编辑转载,其目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请及时与本网联系,我们将在第一时间删除内容!
作者:Amir Fasshihi 流畅的游戏玩法来自流畅的帧率,而我们即将推出的动作平台游戏&Shadow Blade&已经将在标准iPhone和iPad设备上实现每秒60帧视为一个重要目标. 以下是我们在紧凑的优化过程中提升游戏运行性能,并实现目标帧率时需要考虑的事项. 当基本游戏功能到位时,就要确保游戏运行表现能够达标.我们衡量游戏运行表现的 ...
作者:Amir Fasshihi 流畅的游戏玩法来自流畅的帧率,而我们即将推出的动作平台游戏&Shadow Blade&已经将在标准iPhone和iPad设备上实现每秒60帧视为一个重要目标. 以下是我们在紧凑的优化过程中提升游戏运行性能,并实现目标帧率时需要考虑的事项. 当基本游戏功能到位时,就要确保游戏运行表现能够达标.我们衡量游戏运行表现的 ...
/archives/76214 作者:Amir Fasshihi 流畅的游戏玩法来自流畅的帧率,而我们即将推出的动作平台游戏&Shadow Blade&已经将在标准iPhone和iPad设备上实现每秒60帧视为一个重要目标. 以下是我们在紧凑的优化过程中提升游戏运行性能,并实现目标帧率时需要考虑的事项. 当 ...
Unity3D在内存占用上一直被人诟病,特别是对于面向移动设备的游戏开发,动辄内存占用飙上一两百兆,导致内存资源耗尽,从而被系统强退造成极差的体验.类似这种情况并不少见,但是绝大部分都是可以避免的.虽然理论上Unity的内存管理系统应当为开发者分忧解难,让大家投身到更有意义的事情中去,但是对于Unity对内存的管理方式,官方文档中并没有太多的说明,基本需要依 ...
/archives/147913.html 流畅的游戏玩法来自流畅的帧率,而我们即将推出的动作平台游戏&Shadow Blade&已经将在标准iPhone和iPad设备上实现每秒60帧视为一个重要目标. 以下是我们在紧凑的优化过程中提升游戏运行性能,并实现目标帧率时需要考虑的事项. 当基本游戏功能到位时,就 ...
原地址:/msg_221889.html 作者:Amir Fasshihi
流畅的游戏玩法来自流畅的帧率,而我们即将推出的动作平台游戏&Shadow Blade&已经将在标准iPhone和iPad设备上实现每秒60帧视为一个重要目标.
以下是我们在紧凑的优化过程中提升游戏运行性能,并实现目标帧率 ...
Tomcat是一个小型的轻量级应用服务器,也是JavaEE开发人员最常用的服务器之一.不过,许多开发人员不知道的是,Tomcat Connector(Tomcat连接器)有bio.nio.apr三种运行模式,那么这三种运行模式有什么区别呢,我们又如何修改Tomcat Connector的运行模式来提高Tomcat的运行性能呢?
bio bio(block ...
许多朋友都和笔者一样还在使用1GB以下的内存,所以当我们玩3D游戏的时候就会觉得运行有些卡,这个时候除了使关闭游戏以外的所有程序以外,似乎再没有其他节省内存的办法了,其实我们可以在运行游戏前先在任务管理器中结束&explorer.exe&进程,因为它在很多情况下可都是内存耗用大户,结束它可为我们的游戏增加几十MB的可用内存,游戏效果当然会 ...网上会流传一些说法是说在c#中for比foreach指令更加精简,效率更高,而且foreach会在每次循环的时候产生几kb的gc。所以在开发中(Unity)尽量使用for来替代foreach。请原谅我可耻的做个伸手党,请问这个结论在现在依然有效吗? 难道Mono或者微软开发c#的工程师不会对其进行优化吗?还是说这个是一个无法解决的问题?
题主这个问题是U3D的问题而不是C#的问题好吧
“&b&foreach会造成额外的gc开销&/b&”这个坑,在Unity社区里已经是个常识。&br&避免这个坑的方式,是尽量改写为 &b&等价于foreach的for/while代码 &/b&来避免额外的gc开销。&br&&br&关于这个坑的深层原因,在网上很早就有详尽的分析,比如:&br&&a href=&///?target=http%3A///blogs/WendelinReich/841/C_Memory_Management_for_Unity_Developers_part_1_of_3.php& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Memory Management for Unity Developers&i class=&icon-external&&&/i&&/a& 中提到的“Should you avoid foreach loops?”&br&&br&然而关于这个现象的解释,还是有很多“玄学”在到处飞扬,包括题主提到的“&i&for比foreach指令更加精简,更加高效&/i&”等等,终于也在知乎看到了。。。&br&&br&其实,真相当然只有一个:&b&这就是个bug&/b&!&br&&br&以下是我重复了上面那个引文中关于这个bug的分析步骤,同时也是为了验证一下这一bug是否已经在Unity 5以及il2cpp中解决。&br&&br&=====================&br&&br&首先是Unity 5.0.2中写的一段测试代码:(_tl是一个List&int&)&br&&br&&img src=&/dbf3d62f044c3b2e426719_b.jpg& data-rawwidth=&371& data-rawheight=&560& class=&content_image& width=&371&&&br&测试的比较简陋,就是在Update中每次调用不同的Test函数,在Unity的profiler中看一下:&br&&br&TestListForeach()&br&&img src=&/e8fe12d16d1_b.jpg& data-rawwidth=&688& data-rawheight=&115& class=&origin_image zh-lightbox-thumb& width=&688& data-original=&/e8fe12d16d1_r.jpg&&&br&TestListNoForeach()&br&&img src=&/fbdec2a69cc5ff71cb75a24_b.jpg& data-rawwidth=&793& data-rawheight=&76& class=&origin_image zh-lightbox-thumb& width=&793& data-original=&/fbdec2a69cc5ff71cb75a24_r.jpg&&&br&所以简单的测试已经有结论了:&br&&ol&&li&foreach会导致额外的gc对象,每次40B&/li&&li&&b&这个bug在Unity 5(使用mono的情况下)依然存在&/b&&/li&&/ol&=====================&br&&br&那么额外的gc对象是什么呢?肯定是foreach被展开成了什么。。。&br&&br&那就把Unity编出来的Assembly-CSharp.dll(Unity工程目录下Library\ScriptAssemblies中)拉到任意一个反编译工具中看一下呗&br&&img src=&/12ef3a5be6aa7be2c75b3a4ecf2ea237_b.jpg& data-rawwidth=&534& data-rawheight=&190& class=&origin_image zh-lightbox-thumb& width=&534& data-original=&/12ef3a5be6aa7be2c75b3a4ecf2ea237_r.jpg&&&br&恩,foreach这个语法糖被正确展开成while了。这都是符合.Net常识的。&br&&br&这里再提一下 &a data-hash=&957a8e3f6fbd& href=&///people/957a8e3f6fbd& class=&member_mention& data-editable=&true& data-title=&@权然& data-tip=&p$b$957a8e3f6fbd&&@权然&/a& 答案中提到的GetEnumerator的黑历史。这部分历史是真实存在的,GetEnumerator现在是返回struct了。然而问题并不出在这里,否则没法解释我们仅仅通过将foreach改写为while,就能消除多余的gc对象。&br&&br&那还有可能出问题的呢?当然只剩下那个using!&br&这里需要再深入一层,于是我们来看一下生成的最终IL代码:&br&&br&&img src=&/dea6e86b6f_b.jpg& data-rawwidth=&930& data-rawheight=&650& class=&origin_image zh-lightbox-thumb& width=&930& data-original=&/dea6e86b6f_r.jpg&&&br&真相就已经出现了:&br&&b&在finally里,mono编译出来的代码中有一次将valuetype的Enumerator,boxing的过程!!&/b&&br&&br&&What a waste!!!&&br&&br&&b&这就是Unity中所带的老版本mono编译器的一个bug!!!&/b&&br&&br&=====================&br&&br&关于il2cpp的结果:&br&&img src=&/1b34dcf3b3a4ca3df7fe930c26a2fd6e_b.jpg& data-rawwidth=&995& data-rawheight=&953& class=&origin_image zh-lightbox-thumb& width=&995& data-original=&/1b34dcf3b3a4ca3df7fe930c26a2fd6e_r.jpg&&&br&可以看到这里il2cpp是忠实的做了翻译工作,连Box也照样保留了下来,想来想通过il2cpp来解决这个问题也是不现实的了。
“foreach会造成额外的gc开销”这个坑,在Unity社区里已经是个常识。避免这个坑的方式,是尽量改写为 等价于foreach的for/while代码 来避免额外的gc开销。关于这个坑的深层原因,在网上很早就有详尽的分析,比如: …
先说结论,在Unity 4.x的语境下这个观点是正确的,能用for就别用foreach。&br&&br&&b&背景:&/b&&br&foreach会在托管堆上分配内存的问题在早期的C#中也是存在的,原因是foreach会将迭代器转换为IEnumerator。如果迭代器是引用类型,自然会分配在托管堆上;如果是值类型,值类型转换到接口类型是要装箱(boxing)的,需要在托管堆上分配内存并将数据拷贝过去。横竖都躲不过。&br&&br&后来微软在编译器中把这个问题优化掉了,办法是编译时查找名字叫做GetEnumerator的方法,如果提供了一个强类型的迭代器,生成的IL代码就会调用这个版本的GetEnumerator,强类型自然就没有GC的问题了。所以现在的C#里用foreach是没问题的,但是自己实现集合类型的时候记得同时实现一个强类型的IEnumerator&T&给编译器留个后门。&br&&br&&b&而优化代码一定要在实际环境中测量数据。&/b&&br&&br&Unity的问题在于它用的是Mono 2.6,这个版本的Mono编译器还没有做这个优化。Unity的GC性能跟CLR的GC相比差很多,iOS上连JIT都没有,所以这方面还是比较敏感的。&br&&br&&b&优化方法:&/b&&br&&ul&&li&能用for就不用foreach。&/li&&li&把代码用Visual Studio编译成DLL。&/li&&li&用不了for就手动调用强类型版本的GetEnumerator,然后自己写while (e.MoveNext()) ...,最后别忘记调迭代器的Dispose。&/li&&/ul&笨是笨了点,但是在Update等每帧都需要执行的关键代码中可以减少大量GC Alloc,明显改善性能。偶尔才跑一次的话,迭代器一共也分配不了多少内存,Unity GC的Heap Block Size是1KB,能见缝插针的概率还是蛮大的。&br&&br&最后,官方表示Unity 5.x会修复这个问题。(感谢 &a data-hash=&01515eabb32dea23e0c69acc& href=&///people/01515eabb32dea23e0c69acc& class=&member_mention& data-tip=&p$b$01515eabb32dea23e0c69acc&&@王剑飞&/a& 指正)
先说结论,在Unity 4.x的语境下这个观点是正确的,能用for就别用foreach。背景:foreach会在托管堆上分配内存的问题在早期的C#中也是存在的,原因是foreach会将迭代器转换为IEnumerator。如果迭代器是引用类型,自然会分配在托管堆上;如果是值类型,值类型…
已有帐号?
无法登录?
社交帐号登录
没有,没有,还是没有

我要回帖

更多关于 unity c foreach 的文章

 

随机推荐