Unity优化之

Wesley13
• 阅读 357

当我们来创建一个对象、字符串或数组时,我们需要从称为堆的中央池中为其分配内存来存储它。当它不再被使用时,我们又需要来释放这块内存便于重复使用。在以前这个过程通常需要我们通过适当的函数调用显式地分配和释放块内存来实现。但现在,运行时系统如Unity的mono引擎将自动地为我们管理内存。自动内存管理比显式分配/释放需要更少的编码工作,大大减少了内存泄漏的可能性(内存被分配但从来没有随后被释放的情况)。
对此首先要理解两个概念:值类型和引用类型

在方法调用的时候传递的参数,是直接复制到特定的内存区域,以便于方法中使用。如此一来不同的数据类型占用字节不同将很明显的影响到效率。为了避免这种情况的发生,故而出现两种传递方式。值类型通过值传递,引用类型通过引用传递。

在参数传递过程中直接存储和复制的类型称为值类型。包括整型、浮点型、布尔值和结构类型。在堆上分配的,然后通过指针访问的类型称为引用类型,因为存储在变量中的值仅仅是“引用”到实际数据。引用类型的示例包括对象、字符串和数组。

内存分配和垃圾回收
内存管理器跟踪堆中未使用的堆中的区域。当请求一个新的内存块(比如当一个对象实例化时),管理器选择一个未使用的区域来分配块,然后从已知的未使用空间中移除分配的内存。后续请求以相同的方式处理,直到没有足够大的空闲区域分配所需的块大小。不可能出现堆中分配的所有内存都在使用中的情况。只要还存在可以找到它的引用变量,就能访问堆上的引用项。如果所有引用的内存块都消失了(即参考变量被重新分配,或是超出了局部变量的范围)那么它所占用的内存可以被重新分配。
为了确定哪些堆块不再使用,内存管理器搜索所有当前活动的引用变量,并将它们称为“活”的块标记出来。在搜索结束时,内存管理器认为“活”的块之间的任何空间都是空的,可用于后续分配。查找和释放未使用内存的过程称为垃圾收集(简称GC)。

垃圾收集对我们来说是自动的、不可见的,但是收集过程实际上需要大量的CPU时间。如果正确使用,自动内存管理通常会等于或大于手动分配的整体性能。所以我们必须避免引起不比要的gc
例如
1.字符串的连接操作:
string line = intArray[0].ToString();

for (i = 1; i < intArray.Length; i++)
{
line += “, ” + intArray[i].ToString();
}
在每一次循环里都会重新分配内存空间,保存相加之后的字符串,如此一来,循环次数越多内存消耗就越大。
所以当有此需求时应尽量避免“+”操作来连接字符串,可以使用System.Text.StringBuilder类来实现。
更危险的是在unity的update里做字符串连接操作,因为update是每帧更新,所以内存消耗比for循环更大
2.方法返回数组的情况:
float[] RandomList(int numElements)
{
var result = new float[numElements];
for (int i = 0; i < numElements; i++)
{
result[i] = Random.value;
}
return result;
}
这样的方法来生成一个已赋值的数组是很常见,很方便的,但是如果重复调用这样的方法,每次调用都会重新分配新的内存。一旦数组很大,调用频繁的话,空闲的堆内存空间会立马耗尽,从而导致频繁的gc。为了避免这种情况的发生,我们可以利用数组是引用类型的特性,通过传递数组类型的一个参数,然后在方法里改变该数组的值,从而达到目的。修改后的方法如下:
void RandomList(float[] arrayToFill)
{
for (int i = 0; i < arrayToFill.Length; i++)
{
arrayToFill[i] = Random.value;
}
}
如上所述,我们要尽量避免新内存空间的分配,但是无论如何这是不可能完全消除的。我们可以通过两种方式来最小化内存分配带来的消耗
1.如果堆比较小,则进行快速而频繁的垃圾回收
这种方式适用于需要长时间运行,并且帧率稳定的游戏。会频繁的分配小块内存,短暂使用之后快速回收的情况下
可以使用下面的代码,虽然会导致gc的次数变多,但是每次占用的内存都只是小块,
所以垃圾回收也是消耗很小,对游戏的影响不大
if (Time.frameCount % 30 == 0)
{
System.GC.Collect();
}
2.如果堆比较大,则进行缓慢且不频繁的垃圾回收
这种方式适用于不需要频繁的分配和回收内存,并且可以在游戏停顿时处理的游戏。Mono运行时会尽可能地避免堆的自动扩大,所以可以在游戏一开始就分配一块大的内存预存。然后在游戏暂停的时候显示的调用System.GC.Collect()进行
void Start()
{
var tmp = new System.Object[1024];
for (int i = 0; i < 1024; i++)
tmp[i] = new byte[1024];
tmp = null;
}
注意:这两种做法都要慎用的,可以通过查看profiler确定这样做是否真的减少了垃圾收集的时间消耗
3.可重用的对象池
尽可能的减少对象的生成和销毁来避免垃圾生成。例如poolmanager
4.避免装箱操作
装箱:装箱就是隐式的将一个值型转换为引用型对象
int i=0;
Syste.Object obj=i;
装箱操作是非常普遍的一种产生内存垃圾的行为,应尽量避免
5.协程
yield在协程中不会产生堆内存分配,但是如果yield带有参数返回,则会造成不必要的内存垃圾
例如:
yield return 0;
可以用:
yield return null;
代替。
另外一种对协程的错误使用是每次返回的时候都new同一个变量
例如:
yield return new WaitForSeconds(1f);
可以用:
WaitForSeconds delay = new WaiForSeconds(1f);
yield return delay;
代替。

本文分享自微信公众号 - 游戏人的开发分享(No_2SeeYou)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
1年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
Easter79 Easter79
1年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
1年前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序selectfromtable_nameorderiddesc;2.按照指定(多个)字段排序selectfromtable_nameorderiddesc,statusdesc;3.按照指定字段和规则排序selec
Wesley13 Wesley13
1年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
1年前
Java中的OutOfMemoryError的各种情况及解决和JVM内存结构
在JVM中内存一共有3种:Heap(堆内存),NonHeap(非堆内存)\3\和Native(本地内存)。\1\堆内存是运行时分配所有类实例和数组的一块内存区域。非堆内存包含方法区和JVM内部处理或优化所需的内存,存放有类结构(如运行时常量池、字段及方法结构,以及方法和构造函数代码)。本地内存是由操作系统管理的虚拟内存。当一个应用内存不足时
Stella981 Stella981
1年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
1年前
Angular material mat
IconIconNamematiconcode_add\_comment_addcommenticon<maticonadd\_comment</maticon_attach\_file_attachfileicon<maticonattach\_file</maticon_attach\
Wesley13 Wesley13
1年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
helloworld_34035044 helloworld_34035044
5个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为