简介
在使用Unity工具开发游戏的时候经常会被建议不要使用foreach,究其原因说是会产生额外的heap内存,既然存在这种问题, 那我必须得自己搞清楚下。本文是自己查阅资料和试验的结果总结。
本文参考了Memory allocation when using foreach loops in C#
测试环境: Unity5.4.1f1, MonoV2.0.5
剖析
首先来搞明白使用foreach和for的区别,下面是一个简单的例子:
1 |
|
在使用Unity工具开发游戏的时候经常会被建议不要使用foreach,究其原因说是会产生额外的heap内存,既然存在这种问题, 那我必须得自己搞清楚下。本文是自己查阅资料和试验的结果总结。
本文参考了Memory allocation when using foreach loops in C#
测试环境: Unity5.4.1f1, MonoV2.0.5
首先来搞明白使用foreach和for的区别,下面是一个简单的例子:
1 |
|
相比微信支付和支付宝支付要麻烦一些,麻烦的地方主要体现在对测试支付环境的要求以及苹果审核方面的要求上面。本文是自己在接入iOS的IAP模块的经验及总结,发出来分享下。建议需要接入的话还是浏览一遍苹果官方文档
本文是学习Unity官方教程、Unity官方的API和阅读Unity代码的总结
1. AssetBundle由两部分组成:头部(header)数据部分(data segment)
Unity内存释放 问题主要考虑一下几个方面: (1) Resources 加载的资源需要释放的时候调用 Resources.UnloadUnusedAssets();
(2) WWW 加载的任何资源<font color=#FF0000>必须</font>调用WWW的Dispose接口
(3) 创建的RenderTexture<font color=#FF0000>必须</font>调用Release接口
(4) 必须用Xcode联机调试确定C++层有没有内存泄漏
(5) AssetBundle资源在Unity Profile中的Scene Memory不会显示,可以通过Xcode联机调试确定有没有释放完全
(6) 加载资源和卸载资源的异步处理,避免同一帧内加载过多资源导致内存瞬间增大从而导致Crash(低配机器上表现更明显)
一般通过WWW加载一张Texture来通过Image显示的话,一种的方法是使用Texture通过Sprite.Create来创建一个Sprite对象赋值给Image.overrideSprite完成显示。但是需要注意一个问题,Sprite.Create创建对象对拷贝Texture中数据,并生成一个新的Sprite对象,这样会造成耗费将近2倍Texture大小的内存,而且这个内存占用不会随着Image的销毁而马上销毁,知道你调用Resources.UnloadUnusedResources()接口才会销毁。如果在一个场景中有大量的图片需要动态加载的话,那么必须注意内存问题了。所以建议不要使用显示Image的方式,有一种方式是显示RawImage,按照官方文档的描述:
The Raw Image control displays a non-interactive image to the user. This can be used for decoration, icons, etc, and the image can also be changed from a script to reflect changes in other controls.
RawImage是一个没有和用户交互的Image,所以如果我们不需要比如事件这种交互的操作的话,还是建议用RawImage来显示Texture。RawImage的方式会高效很多,如果从AssetBundle里面加载了一张Texture,不管是赋值给多少个RawImage,Texture是公用的,只会有一分内存。
本文是阅读CLR via C#和 Theading in C#做的个人总结
多个线程同时访问共享数据时有可能对数据造成损坏,线程同步的目的就是保证多线程访问共享数据时数据的安全性。在C#中能够对一个变量中的所有字节都一次性读取或写入,我们就认为这个变量类型具有原子性,CLR保证对以下类型变量的读写是原子性的: Boolean, Char, (S)Byte, (U)Int16, (U)Int32, (U)IntPtr,Single。
不具备原子性的类型变量的读取或者写入操作会被分割,也就是说对x变量的读取或者写入操作需要多条指令完。对于变量x来说,赋值操作需要两个MOV指令来完成即:
1 |
|
那么在多线程的情况下会出现另外一个读取该值的操作取出来的值是0x0123456700000000,所以<font color=#FF4040>我们要对非原子性的类型变量在多线程访问的情况下要进行同步操作(类似于自己构造变量的原子性),以保证我们获取的数据的完整性</font>。
这两类数据都是作为共享数据,可以让多个线程同时访问的。所以我们要对其进行同步操作。但是当多个线程同时对共享数据进行只读操作的时候是不需要进行任何数据同步的。
Monitor为static类,它的工作原理是操作对象的同步快索引。CLR在初始化的时候会在堆中分配一个同步块数组,这个数组的元素用来关联new构造器构造出来的对象的同步索引。一个对象在构造时它的同步块索引初始化为-1,表示不引用任何同步索引块。然后调用Monitor.Enter的时候,CLR在数组中找到一个空白的同步块来关联当前Monitor传入的对象,对象的同步块索引将会自增,对象处于锁定状态。当调用Monitor.Exit的时候如果没有其他线程等待这个对象,如果没有那么Exit将对象的同步块索引设置回-1,对象处于自由状态。
1 |
|
Mutex类强制线程标识,只能由获得它的线程可以释放互斥体。Mutex内部维护着一个递归计数,拥有互斥锁的线程可以递归调用相同互斥体(互斥对象)WaitOne而不会阻止线程执行。但是必须调用相同次数的ReleaseMutex来释放互斥体。
1 |
|
内部维护者一个Int32变量。信号量为0时在信号量上等待的线程会阻塞,信号量大于0时解除阻塞。在信号量上等待的线程解除阻塞时,内核自动从信号量的计数中减1。
ReaderWriterLockSlim
在Monitor、Mutex和Semaphore方式来同步线程时就算某一时刻多个线程来同时读取数据也会造成只有一个线程工作其他线程等待的情况,这样就在成了资源的浪费。ReaderWriterLockSlim基于多个线程对共享数据同时读取不需要同步这个点来做的优化。它的工作方式如下:
(1) 一个线程向数据写入时,请求访问的其他所有线程都被阻塞
(2) 一个线程从数据读取时,请求读取的其他线程能够继续执行,但请求写入的线程仍然被阻塞
(3) 写入的线程结束后,要么解除一个写入线程的阻塞,要么解除所有的读取线程的阻塞。如果没有线程被阻塞则锁就进入自由状态
(4) 读取线程结束后,一个写入的线程被解除阻塞(如果有)。如果没有线程被阻塞则锁进入自由状态