【小松教你手游开发】【unity实用技能】Unity项目内存优化大全

http://gad.qq.com/article/detail/7173927


前言
  手游项目开发中,每个项目都会遇到或多或少的内存问题。本文涉及到了Unity项目:启动内存,Mono内存,System 内存这三个方面。对于为什么标题叫《内存优化大全》,主要是在自己职业生涯中,《代码大全》给我带来了很多的启发和帮助。这篇文章希望帮到需要的人。     

启动内存
  在刚开发Unity项目时,遇到了一个非常棘手的问题。游戏启动时内存占用非常高(90M)。我使用二分法,排查是哪里分配的内存。但是结果令我非常的不解,因为当我用二分法,一直排除到程序启动至加载一个场景,一行代码都不执行,但App启动后内存占用缺还是很高(85M)。为了排除场景有未排除的代码, 新建了空白scene。 在google上搜索了好久关于启动内存高的问题,都没有得到答案。此刻只好怀疑到资源这块,通过删除Resources下的资源,神奇的事情发生了,启动内存降低了。
  直到看Unite 2006的开发者大会性能优化演讲,才看到Unity会根据Resources目录下的资源生产对应的Entity信息,在App启动时会加载所有的资源信息,资源越多,对应需要的内存越多,时间越久。这么重要的信息Unity官方尽然没有任何说明,这令我非常惊奇。
  我这边的项目解决办法就是使用AssetBundle,Resources目录下放少量的资源,解决了问题。官方在Unite 2016大会上也是给出的相同的方案,希望能帮到大家。 

heap 内存优化
  boxing
  值类型转换到应用类型时,需要在堆上申请内存
1
2
int a = 2;
object b = a;     // 这里会有内存申请
  项目中常见误用。enum类型当做Dictionary key, 由于会调用GetHashCode, Equals 会导致boxing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class EnumCompare : IEqualityComparer
{
     public bool Equals(ConfigTypeEnum x, ConfigTypeEnum y)
     {
         return (x - y) == 0;
     }
 
     public int GetHashCode(ConfigTypeEnum obj)
     {
         return ( int )obj;
     }
}
void TestDictionary()
{
     Dictionaryint=""> enumDic = new Dictionaryint="">(new EnumCompare());
    //Dictionary enumDic = new Dictionary();
    enumDic.Add(ConfigTypeEnum.System, 1);
    enumDic.Add(ConfigTypeEnum.Config, 2);
    enumDic.Add(ConfigTypeEnum.Network, 3);
 
 
    int len = 100000;
    for(int i = 0; i < len; ++i)
    {
        int id = enumDic[ConfigTypeEnum.System];         // 这里如果不添加EnumCompare,会调用object 的 Equals, GetHashCode
    }
 
    Dictionary<int, int=""> intDic = new Dictionary<int, int="">();
    intDic.Add(1, 1);
    intDic.Add(2, 2);
    intDic.Add(3, 3);
 
    for(int i = 0; i < len; ++i)
    {
        int id = intDic[1];                             // 这里没什么问题
    }
}

foreach
  使用foreach会在首次调用时生成一个Enumerator, 如果在Update里大量调用,还是非常影响效率的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
List< int > m_userIdList = new List< int >
{
     100001,
     100002,
     100003
};
 
// bad
foreach (var iter in m_userIdList)
{
     Debug.Log( "user id:" + iter);
}
 
// good
int idCount = m_userIdList.Count;
for ( int i = 0; i < idCount; ++i)
{
     Debug.Log( "user id:" + m_userIdList[i]);
}

unity api
  Unity api 返回数组时,总会返回一个新的数组,因此一定要注意调用次数
1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
for ( int i = 0; i < Input.touches.Length; ++i)
{
     Touch touch = Input.touches[i];
}
 
// good
Touch[] touchArr = Input.touches;
int len = touchArr.Length;
for ( int i = 0; i < len; ++i)
{
     Touch touch = touchArr[i];
}
string
  初始化时,使用常量string.Empty,不要使用 ""
1
2
3
4
5
// bad
string str = "" ;
 
// good
string str = string .Empty;
  字符串连接,调用字符串operator+,产生零时变量, string 构造会在堆上申请内存。如果调用量大,使用StringBuilder
1
2
3
4
5
6
7
8
9
string str1 = "hello," ;
string str2 = "world" ;
 
// bad
string str = str1 + str2;
 
// good
System.Text.StringBuilder sb = new System.Text.StringBuilder();
str = sb.Append(str1).Append(str2).ToString();
匿名函数
  匿名函数的本质是生成实例对象,会在堆上申请内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private int Cmp( int a, int b)
{
     return a - b;
}
 
private void TestDelegate()
{
     List< int > idList = new List< int >();
 
     // bad
     idList.Sort( delegate ( int a, int b)
     {
         return a - b;
     });
 
     // good
     idList.Sort(Cmp);
}</ int ></ int >
  匿名函数用起来确实简介明晰,但是这是游戏开发,如果在Update 每帧调用,这里的优化还是非常有必要的。

成员变量
  先看代码,后面说明STL的最佳实践
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Player
{
     private List< int > m_buffIdList;
     private List m_effectList;
     private const int kBuffIdMaxCount = 100;
     private const int kEffectMaxCount = 200;
 
     public void Init()
     {
         m_buffIdList = new List< int >(kBuffIdMaxCount);
         m_effectList = new List(kEffectMaxCount);
     }
 
     public void Dispose()
     {
         m_buffIdList.Clear();
         m_effectList.Clear();
     }
}
  Unity的Mono heap 内存只会增长,不会减少,这句话是需要深入理解
a、Mono heap 由于不会整理内存空间,所以当内存出现空洞,但是需要新内存时,洞的空间不够,则申请新的内存
b、如果一开始全部申请好所有所需的内存空间,Mono heap内存并不会增长

  对应到成员变量的使用,在STL 内存申请方面,如果内存容量不足,则申请2的N次方空间,所以如果能知道最大使用空间,即可很好的节省不必要的申请。
并且当调用Clear 函数时,内存并不会被回收,只是将index置为0

内存碎片
  针对内存碎片,最好的方式就是用对象池,重复使用对象,这里需要注意的是在设计重复使用的对象一定要实现好Dispose函数,和Init函数。

资源内存
  对于资源占用内存,主要是资源需要有相关的规定,以及参数,参数部分需要考虑一下几点:
a、贴图设置为read,如果write的话,内存里会有一份拷贝
b、UI贴图勾选掉minimap
c、贴图合并为2的n次方大图

在合适的实际调用函数,比如场景切换时:
1
2
Resources.UnLoadUnUsedAssets();
System.GC.Collect();
总结
  合理的美术资源规范,以及流程,加上对Unity内存使用和回收的深入理解,是保证项目质量的不二之法。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值