2.21 性能优化

day 21 性能优化
一、程序性能的分析
1,Profiler工具是Unity3D提供的一 套用于实时监控资源消耗的工具,通过使用该工具,我们可以直观地查看程序 运行时各个方面资源的占用情况,并匀速找到影响程序性能的线程和函数,再 针对性的优化.
我们可以使用快捷键Ctrl+7快捷键或依次点击“Window->Profiler”命令来调出Profiler窗口。
下方是Profiler工具中各项参数的意义
http://blog.sina.com.cn/s/blog_80cc3d870102vv6k.html
Profilder显示了当前场景中所有对象所消耗的资源情况,对实际开发起着非 常重要的作用,通过该工具的使用,使得开发人员能够通过查看资源消耗分布,来推测程序中的不足之处。这同时也体现出了Unity3D引擎的强大之处.
2,帧调试器(Frame Debugger)的应用
一个针对渲染的调试器。与其他的调试工具的复杂性相比,Unity原生的帧调 试器非常的简单便捷。我们可以使用它来看到游戏图像的某一帧是如何一步步渲染出来的需要使用帧调试器,我们首先需要在Window->Frame Debugger中打开帧调试器窗口。

3,其他性能分析工具
对于移动平台上的游戏来说,我们希望得到真机上运行游戏时的性能数据,这 时Unity目前提供的各个工具可能就不能满足我们的需求。 对于安卓平台来说,高通的Adreno分析工具可以对同的测试机进行详细的性能 分析。英伟达提供了NVPerfHUD工具来帮助我们得到几乎所有需要的性能分析 数据,例如:每个DrawCall的GPU时间等等。 对于iOS平台,Xcode中的OpenGL ES Driver Instruments可以给出一些宏观上 的性能信息.

二,CPU优化
CPU优化分为四个部分:
1、DrawCall:Unity每次再准备数据并通知GPU渲染的过程被称为一次DrawCall。 优化方案:批处理

动态批处理
好处:一切处理都是自动的,不需要我们自己做任何操作,而且物体是可以移动 的。
坏处:限制很多,可能一不小心我们就会破坏了这种机制,导致Unity无法批处理 一些使用了相同材质的物体。
静态批处理
好处:自由度很高,限制很少。
坏处:可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动 了。

2、物理组件的使用 优化方案: <1>设置一个合适的Fixed Timestep <2>不要使用MeshCollider(从优化的角度上来说,我们尽量减少使用物理组建).

3、GC(Garbage Collection垃圾回收)
优化方案:
1、字符串连接的处理。因为将两个字符串连接的过程,其实是生成一个新的字符串的过程。而之前的旧的字符串自然而然就成为了垃圾。而作为引用类型的字符串,其空间是 在堆上分配的,被弃置的旧的字符串的空间会被GC当做垃圾回收。
2、尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传 说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。
3、不要直接访问gameobject的tag属性。比如if (go.tag == “human”)最好换成if (go.CompareTag (“human”))。因为访问物体的tag属性会在堆上额外的分配空间。如 果在循环中这么处理,留下的垃圾就可想而知了。
4、使用“池” ,以实现空间的重复利用。
5、最好不用LINQ的命令,因为它们会分配临时的空间,同样也是GC收集的目标。在某些 情况下LINQ无法很好的进行AOT编译。比如“OrderBy”会生成内部的泛型类 “OrderedEnumerable” 。这在AOT编译时是无法进行的,因为它只是在OrderBy的方法中 才使用。所以如果你使用了OrderBy,那么在IOS平台上也许会报错。

4、代码质量
1、不要频繁使用GetComponent,尤其是在循环中.
2、善于使用OnBecameVisible()和OnBecameVisible().
3、使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0).
4、善于使用ref关键字
5、将经常需要使用的属性查询缓存起来
6、不要频繁使用Instantiate和Destroy.
7、习惯性的将暂时不用的GameObject设置为非激活

三,GPU优化
1、减少绘制的数目
解决方案:模型的LOD技术、遮挡剔除技术
模型的LOD技术:
在Unity中,我们可以使用LODGrounp组件来为一个物体构建一个LOD.我们需 要为同一个对象准备多个包含不同细节程序的模型,然后把它们赋给LODGroup 组件中的不同等级,Unity就会自动判断当前位置上需要使用哪个等级的模型。 同样它的缺点同样是需要占用更多的内存,而且如果没有调整好距离的话,可能会造成模拟的突变。
遮挡剔除技术 :
依次点开Window->Oxxlusion Culling 打开遮挡剔除窗口
2、优化显存的带宽

四,内存优化
1、Resource资源不用的要删除,纹理、网格、音频等等;
2、在CPU压力不是特别大的时候,重置GameObject、组件等占用的内存;
3、在大AssetBundle的时候,可以考虑bundle的压缩。


使用“池”的方法:
新建Pool文件夹管理其代码:
1,建Item类:存放预设体

//保存的物体
public class Item
{
public GameObject go; //保存的游戏物体
public bool isHide; //是否被隐藏
public float saveTime; //进入池子的时间

public Item(GameObject gameObject)
{
go = gameObject;
}

//显示
public GameObject Active()
{
go.SetActive(true);
isHide = false;
return go;
}

//隐藏
public void Hide()
{
go.SetActive(false);
isHide = true;
saveTime = Time.time;
}

//判断有没有过期
public bool IsOverTime()
{
if (isHide==false)
{
return false;
}
if (Time.time - saveTime >= PoolManager.overTime)
{
return true;
}
return false;
}
}

2,建subPool类管理子池子,将物体存放其中,通过每个物体的哈希值判断

//子池子
public class SubPool{

public string _name;//标示符

public SubPool(string _name)
{
this._name = _name;
}
//第一个参数GameObject的哈希值,第二个值保存的GameObject
public Dictionary<int, Item> objectDic = new Dictionary<int, Item>();

//添加物体
public void Add(GameObject gameObject)
{
int hashCode = gameObject.GetHashCode();
if (!objectDic.ContainsKey(hashCode))
{
objectDic.Add(hashCode, new Item(gameObject));
}
}

//取出在池子中隐藏的物体
public GameObject GetObject()
{
foreach (Item item in objectDic.Values)
{
if (item.isHide)
{
return item.Active();
}
}
return null;
}

//隐藏
public void Hide(GameObject gameObject)
{
int hashCode = gameObject.GetHashCode();
if (objectDic.ContainsKey(hashCode))
{
objectDic[hashCode].Hide();
}
}

//删除
//删除单个
public void RemoveSingle(GameObject gameObject)
{
int hashCode = gameObject.GetHashCode();
if (objectDic.ContainsKey(hashCode))
{
GameObject.Destroy(gameObject);
objectDic.Remove(hashCode);
}
}

//删除所有
public void RemoveAll()
{
List<Item> list = new List<Item>();
foreach (Item item in objectDic.Values)
{
list.Add(item);
}
while (list.Count>0)
{
if (list[0] != null && list[0].go != null)
{
GameObject.Destroy(list[0].go);
list.RemoveAt(0);
}
}
objectDic = new Dictionary<int, Item>();
}

//删除过期
public void RemoveOverTime()
{
List<Item> list = new List<Item>();
foreach (Item item in objectDic.Values)
{
if (item.IsOverTime())
{
list.Add(item);
}
}
for (int i = 0; i < list.Count; i++)
{
RemoveSingle(list[i].go);
}
}
}

3,最后建一个管理类管理池,
//对象池管理
public class PoolManager {

public const float overTime = 6;

//保存不同类型的对象池
public static Dictionary<string, SubPool> subpools = new Dictionary<string, SubPool>();

//添加
public static void Add(string _name,GameObject gameObject)
{
if (!subpools.ContainsKey(_name))
{
//创建新的子池子来保存gameObject
subpools.Add(_name,new SubPool(_name));
}
subpools[_name].Add(gameObject);
}

//取出
public static GameObject GetObject(string _name)
{
if (subpools.ContainsKey(_name))
{
return subpools[_name].GetObject();
}
return null;
}

//隐藏
public static void Hide(string _name, GameObject gameObject)
{
if (subpools.ContainsKey(_name))
{
subpools[_name].Hide(gameObject);
}
}

//删除单个
public static void RemoveSingle(string _name, GameObject gameObject)
{
if (subpools.ContainsKey(_name))
{
subpools[_name].RemoveSingle(gameObject);
}
}

//删除所有
public static void RemoveAll()
{
//遍历所有的池子
foreach (SubPool subpool in subpools.Values)
{
subpool.RemoveAll();
}
subpools = new Dictionary<string, SubPool>();
}

//删除过期
public static void RemoveOverTime()
{
foreach (SubPool subpool in subpools.Values)
{
subpool.RemoveOverTime();
}
}
}

4,通过调用类的方法完成加载

拓展:编译器所占的程序分配区:
1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。空间小,几M.
  2、堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。空间较大
  3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
  4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放。
  5、程序代码区
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值