Cpu优化大全 之 代码优化

本文分享了Unity游戏开发中提升性能的多种方法,包括资源管理、代码优化、算法改进等方面,帮助开发者解决游戏启动慢、内存占用高等问题。
前言
观察AppStore中游戏应用的评论,玩家对手机发热这一项评论的非常多。在玩家眼里这是游戏优化的不够好,太耗电。主要原因是CPU运算量大。这篇文章从两个方面介绍如何提高运算效率。其中“使用技巧”主要介绍在Unity研发中一些使用不当的方式,以及如何修正。“算法优化”项算是抛砖引玉,算法的优化总是无止境的,不同项目类型都有自己需求。

使用技巧
神奇的启动时间
在刚开发Unity项目时,遇到了一个非常棘手的问题。游戏启动时内存占用非常高(90M)。我使用二分法,排查是哪里分配的内存。但是结果令我非常的不解,因为当我用二分法,一直排除到程序启动至加载一个场景,一行代码都不执行,但App启动后内存占用缺还是很高(85M)。为了排除场景有未排除的代码, 新建了空白scene。 在google上搜索了好久关于启动内存高的问题,都没有得到答案。此刻只好怀疑到资源这块,通过删除Resources下的资源,神奇的事情发生了,启动内存降低了,自然启动速度就非常快。

直到看Unite 2006的开发者大会性能优化演讲,才看到Unity会根据Resources目录下的资源生产对应的Entity信息,在App启动时会加载所有的资源信息,资源越多,对应需要的内存越多,时间越久。这么重要的信息Unity官方尽然没有任何说明,这令我非常惊奇。

我这边的项目解决办法就是使用AssetBundle,Resources目录下放少量的资源,解决了问题。官方在Unite 2016大会上也是给出的相同的方案,希望能帮到大家。 

延迟解析
在项目设计过程中,我们经常将加载解析配置当做完整的模块,将所有配置文件加载并解析。由于许多模块都依赖配置文件,一般将配置文件启动时加载,当项目大的时候,会发现这里非常拖慢启动速度。这里将配置文件分为以下几类:
  1. 提前加载配置文件,启动时加载
    1. 尤其运行必须的配置文件,比如版本号,远端服务器地址
    2. 影响游戏启动后显示的配置,因为加载文件有卡顿的情况,所以放在loading时加载是值得的
  2. 所需及所求的文件,当使用时加载,解析,缓存
数组遍历
数组的遍历非常常见,尤其在使用Unity Api是更加需要注意,Unity Api 返回数组每次都会返回新的数组,如果每次都访问数据,不仅分配了不必要的内存,还增加cpu调用次数。
1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
// 这里调用了 n的2次方 Input.touches ,n = Input.touches.Length;
for ( int i = 0; i < Input.touches.Length; ++i)
{
     Touch touch = Input.touches[i];              
}
 
// good
// 这里只调用了 1次 Input.touches
Touch[] touchArr = Input.touches;
int len = touchArr.Length;
for ( int i = 0; i < len; ++i)
{
     Touch touch = touchArr[i];                   
}
内联函数
C#并没有C++的内联函数,在类内部使用时使用成员变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Player
{
     private int m_playerId;
     public int playerId
     {
         get { return m_playerId; }
         set { m_playerId = value; }
     }
 
     private void Update( int id)
     {
         // bad
         playerId = id;             // 这里会调用 get 函数
 
         // good
         m_playerId = id;          // 直接访问成员变量
     }
}
对于继承MonoBehaviour,常用的变量如果大量调用, 可定义成员变量指向。
1
2
3
4
5
6
7
8
9
10
private Transform m_transform;
void Start()
{
     m_transform = transform;
}
 
void Update()
{
     m_transform.Translate( new Vector3(1, 1, 0), Space.World);     // 大量调用时,减少调用链
}
SendMessage()
此函数是用来向特定对象发送消息函数,由于会遍历对象所有节点,效率不高。通过监听者模式,效率更高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static class MessageMgr
{
     public static void  RegisterMsg( string msgType, System.Action callBack)
     {
 
     }
 
     public static void DispatchMsg( string msgType)
     {
 
     }
}
 
void SendMsgToPlayer()
{
     // bad
     SendMessage( "InitPlayer" , this );     // 会遍历整个Object的子节点,判断是否有InitPlayer方法
 
     MessageMgr.DispatchMsg( "MsgInitPlayer" );     // 自定义消息管理, 定向发送消息,效率更高
}

反射
反射在编译到IOS平台时,并没有明确说明转换过程,使用时需要测试方可放心使用,下面说说遇到一个问题。
1
2
3
// 这里会在ios上分配内存,如果非要使用,并且调用次数很多,做缓存
System.Type t = System.Type.GetType( "Player" );         
Player p = (Player)System.Activator.CreateInstance(t);
p.playerId = 10001;
携程
携程有效的分摊cpu 最大峰值。下面的例子介绍,如何分段加载游戏场景。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void LoadLevel()
{
     int level = 2;
     StartCoroutine(LoadLevelCoroutine(level));
}
 
IEnumerator LoadLevelCoroutine( int level)
{
     // load secene
     yield return new WaitForEndOfFrame();
 
     // load ui
     yield return new WaitForEndOfFrame();
 
     // load audio
     yield return new WaitForEndOfFrame();
 
     // load actor
     yield return new WaitForSeconds(1.0f);
}
第三方库
使用效率高的库,在使用第三方库的时候,多做比较分析,得出最优的效率库。
1
2
3
4
PlayerInfo litPlayerInfo = JsonMapper.ToObject(litJsonStr);     // LitJson
 
PlayerInfo unityJsonInfo = JsonUtility.FromJson(litJsonStr);     // Unity 自带的JsonUtility 效率差很多倍

缓存
多使用缓存, 不要重复计算,下面的例子,一个是每次在使用数据时解析json,另一个是解析好后,每次调用直接使用缓存。
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
[System.Serializable]
class PlayerConfig
{
    public int id;
    public string name;
}
 
private PlayerConfig m_config;
private string m_jsonStr;
private PlayerConfig ParseConfig( string jsonStr)
{
     return JsonUtility.FromJson(jsonStr);
}
private void InitConfig()
{
     m_jsonStr = "{\"id\":1001, \"name\":\"lfwu\"}" ;
     m_config = ParseConfig(m_jsonStr);
}
 
private void Update()
{
     // bad
     int id = ParseConfig(m_jsonStr).id;
 
     // good
     id = m_config.id;                  
}
字符串
字符串比较时,.net 涉及到语言相关项,会拖累速度,建议使用string.Equals,Compare的速度很慢, 因此在使用 string.IndexOf(), string.LastIndexOf() 需要注意语言相关问题。
1
2
3
4
5
6
7
8
9
10
11
12
string str1 = "hello, world" ;
string str2 = "hell0, wor1d" ;
 
// bad
int ret = 0;
ret = string .Compare(str1, str2);     // 语言相关,速度慢
ret = str1.CompareTo(str2);           // 语言相关,速度慢
 
// good
bool isSame = false ;
isSame = str1.Equals(str2);                                  
isSame = str1.Equals(str2, System.StringComparison.Ordinal);  // 速度快,按字符2进制比较
Dictionary
使用ContainsKey判断,在调用取值,调用了两次取值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dictionary< int , string = "" > playerDic = new Dictionary< int , string = "" >
{
     { 10001, "lfwu" },
     { 10002, "xiaoy" }
};
 
int id = 10001;
if (playerDic.ContainsKey(id))
{
     return playerDic[id];
}
else
{
     return string .Empty;
}
建议使用TryGetValue。
1
2
3
4
5
6
7
8
9
10
11
Dictionary< int , string = "" > playerDic = new Dictionary< int , string = "" >
{
      { 10001, "lfwu" },
      { 10002, "xiaoy" }
};
 
int id = 10001;
string ret = string .Empty;
playerDic.TryGetValue(id, out ret);
return ret;
内置函数
在创建脚本继承MonoBehaviour时,删掉默认不用的函数,这些函数都会被调用。
1
2
3
void Awake() {}
void Start() {}
void Update() {}
Update
在戏开发中Update调用非常常见,Unity中类继承MonoBehaviour,会自动注册Update函数。
1
2
3
4
5
6
7
8
9
private List m_updateList = new List();
private void Update()
{
     int len = m_updateList.Count;
     for ( int i = 0; i < len; ++i)
     {
          m_updateList[i]();
     }
}
在一个对象里通过遍历所有对象的函数效率会比分别在每个对象里自己调用Update效率高很多

设置帧率
Unity默认帧率是60,针对不同的游戏可以设置不同的帧率,设置合理的帧率,提高性能,减少cpu调用次数。
1
Application.targetFrameRate = Config.kFrameRate;
算法优化
遇到被除数是2的时候,可以改为乘法运算。
1
2
3
4
5
// bad
float ret = dis / 2.0f;
 
// good
float ret = dis * 0.5f;
遇到两点之间的距离是否接近。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Vector3 posA = new Vector3(1, 1, 1);
Vector3 posB = new Vector3(2, 2, 2);
 
// bad
float minDis = 10.0f;
float dis = Vector3.Magnitude(posA - posB);     // 这里调用了开平方,效率比较低
 
if (dis < minDis)
{
 
}
 
// good
float sqrMinDis = 10.0f * 10.0f;
float sqrDis = Vector3.SqrMagnitude(posA - posB);     // 平方和,速度很快
if (sqrDis < sqrMinDis)
{
 
}
开平方,那让我们看下传奇Quake代码里的开平方。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float Q_rsqrt( float number )
{
     long i;
     float x2, y;
     const float threehalfs = 1.5F;
 
     x2 = number * 0.5F;
     y  = number;
     i  = * ( long * ) &y;                      // evil floating point bit level hacking
     i  = 0x5f3759df - ( i >> 1 );              // what the fuck?
     y  = * ( float * ) &i;
     y  = y * ( threehalfs - ( x2 * y * y ) );  // 1st iteration
 
     return y;
}
总结
只有对自己对每一行代码的性能消耗胸有成竹,才能做到如庖丁解牛那般轻松。
@echo off ::automatic updates -windows自动更新,靠,滚! sc stop wuauserv sc config wuauserv start= disabled ::clipbook - 用与局域网电脑来共享 粘贴/剪贴的内容。(靠,想得出!) sc stop clipsrv sc config clipsrv start= disabled ::com+Event system -一些 COM+ 软件需要,检查你的 c:\program files\ComPlus Applications 目录,没东西可以把这个服务关闭. sc stop eventsystem sc config eventsystem start= disabled ::COM+Event system application -同上 sc stop comsysapp sc config comsysapp start= disabled ::COmputer browser - 用来浏览局域网电脑的服务,但关了也不影响浏览!垃圾 sc stop Browser sc config Browser start= disabled ::DHCP client-静态IP者需要(xDSL 等)小猫就不用了!! sc stop dhcp sc config dhcp start= disabled ::Distributed link tracking client-用于局域网更新连接信息,比如在电脑A有个文件,在B做了个连接,如果文件移动了,这个服务将会更新信息。占用4兆内存。 sc stop trkwks sc config trkwks start= disabled ::Distributed Transaction coordinator-无聊的东西。 sc stop msdtc sc config msdtc start= disabled ::DNS Client-DNS解析服务。。无聊~~ sc stop dnscache sc config dnscache start= disabled ::Fast user switching compatibility-多用户快速切换服务..无聊 sc stop fastuserswitchingcompatibility sc config fastuserswitchingcompatibility start= disabled ::IMAPI CD-burning COM service -xp刻牒服务,用软件就不用了占用1。6兆内存 sc stop imapiservice sc config imapiservice start= disabled ::IPSEC Services-大众用户连边都沾不上。 sc stop policyagent sc config policyagent start= disabled ::Logical Disk manager -磁盘管理服务。。需要时它会通知你,所以一般关。 sc stop dmserver sc config dmserver start= disabled ::Logical Disk manager administrative service-同上。 sc stop dmadmin sc config dmadmin start= disabled ::messenger -不是msn,不想被骚扰的话就关。注:妖刺就是利用这个。 sc stop messenger sc config messenger start= disabled ::Net Logon-登陆domai controller 用的,大众用户快关! sc stop netlogon sc config netlogon start= disabled ::Netmeeting remote desktop sharing-用netmeeting实现电脑共享。。晕!关! sc stop mnmsrvc sc config mnmsrvc start= disabled ::Network DDE -和clipbook一起用的,无聊~~~~ sc stop netdde sc config netdde start= disabled ::Network DDE DSDM -同上 sc stop netddedsdm sc config netddedsdm start= disabled ::Network Location Awareness-如有网络共享或ICS/ICF可能需要.(服务器端) sc stop nla sc config nla start= disabled ::NT LM Security support provider-telnet 服务用的东东,关!! sc stop ntlmssp sc config ntlmssp start= disabled ::NVIDIA Driver Helper service -nvidia 显卡帮助,关! sc stop dnscache sc config dnscache start= disabled ::Portable media serial number-绝对无用,无聊之及。 sc stop wmdmpmsn sc config wmdmpmsn start= disabled ::QoS RSVP -关!就是那个20%的 QoS sc stop rsvp sc config rsvp start= disabled ::Remote desktop help session manager-远程帮助服务,傻透,占用4兆内存。 sc stop rdsessmgr sc config rdsessmgr start= disabled ::remote registry -远程注册表运行/修改。大漏洞,还不快关!! sc stop remoteregistry sc config remoteregistry start= disabled ::routing and remote access-哈哈。。不知者关! sc stop remoteaccess sc config remoteaccess start= disabled ::secondary logon-给与administrator 以外的用户分配指定操作权.晕~~~ sc stop seclogon sc config seclogon start= disabled ::server -局域网文件/打印共享需要的。 sc stop lanmanserver sc config lanmanserver start= disabled ::smart card -关!1。4兆内存 sc stop scardsvr sc config scardsvr start= disabled ::SSDP Discovery service-没有什么硬件利用这个服务。。 sc stop ssdpsrv sc config ssdpsrv start= disabled ::system restore service -系统还原服务,吃资源和内存的怪兽。。虽然有时用到,自己决定。 sc stop srservice sc config srservice start= disabled ::TCP/IP NetBIOS helper-如果你的网络不用 Netbios 或WINS,关了. sc stop lmhosts sc config lmhosts start= disabled ::telnet -大漏洞,我第一个关的就是这个.这根dos中 telnet 命令没关系。2兆内存。 sc stop tlntsvr sc config tlntsvr start= disabled ::universal plug and play device host-同SSDP Discovery Service ,没用. sc stop upnphost sc config upnphost start= disabled ::volume shadow copy-同MS Software Shadow Copy Provider,无用. sc stop vss sc config vss start= disabled ::Windows Installer -windows的MSI安装服务,建议设成手动。 sc stop msiserver sc config msiserver start= demand
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值