游戏测试 游戏开发中常见细节优化实践

不二 for 光娱游戏 · 2020年05月21日 · 2524 次阅读

文章分两部分
一是自己最近使用 Perfdog 也发现了一些常用的技巧,现在安利给大家一下
二是一些在游戏开发中常见到的优化技巧

一 PerfDog 使用技巧篇

1.双击批注
基本使用不提,在整个测试过程我们经常会遇到很多场景,每个场景的性能数据一般都会各有不同,所以为了在报告中看的更明显,我们可以增加批注,比如标记关键节点等。
==鼠标左键双加添加批注==
批注及标定 (鼠标左键双击,则批注。左键双击已生成的批注,则取消。鼠标左键单击,则标定):
在这里插入图片描述
2.场景添加标签

为了更加明显区分我们的 测试场景,我们可以对阶段时间增加标签,
==通过标签按钮给性能数据打标签,鼠标左键双击颜色区域可修改对应区域标签名==
在这里插入图片描述
比如我的标记完了就是这样
在这里插入图片描述
现在我们来看一下报告的样式
在这里插入图片描述
这样对于场景区分是不是明显多了。
3.保存具体数据信息
有时我们需要具体的记录下每一帧运行的具体数据,我们有两种办法:
1.鼠标左键框选后右键存储
在这里插入图片描述
2.是测试完后上传数据到云端时选择同时保存到本地
这样就可以把数据保存到具体的 Xlsx 里,默认在性能狗的 data/测试的应用包名/测试时间文件夹。
在这里插入图片描述
4.多进程测试
iOS 平台,APP 多进程分为 APP Extension 和系统 XPC Server。
比如:某电竞直播软件用到 APP Extension 扩展进程 (扩展进程名 LABroadcastUpload)。当然也可能用到系统 XPC Server 服务进程,如一般 web 浏览器会用到 webkit。
Android 平台,一般大型 APP,比如游戏有时候是多进程协作运行(微信小游戏,微视等 APP 及王者荣耀等游戏多子进程),可选择目标子进程进行针对性测试。默认是主进程;
==子程序进程名高亮显示,表示当前子进程处于顶层==
在这里插入图片描述
5.数据对比
首先在 web 后台上选择所在比对的数据
在这里插入图片描述选择完毕后打开对比界面就可以对比历史测试用例的数据啦,FPS,cpu,内存,GPU,网络,耗电量啦都可以对比,十分便捷。
在这里插入图片描述
更详细的的使用说明可以在这里查看使用说明

下面简单分享一下在游戏开发中一些细节优化

二游戏开发中一些细节优化

1.能整不浮,能乘不除

看一下代码

float f_a = 66666888f;
float f_b = f_a + 0.01f;
Debug.Log(f_a);//1.677722E+07
Debug.Log(f_b);//1.677722E+07
Debug.Log("_______________****_______________");
double d_a = 9007199254740992f;
double d_b = d_a + 0.01f;
Debug.Log(d_a);//9.00719925474099E+15
Debug.Log(d_b);//9.00719925474099E+15

输出结果;
在这里插入图片描述
实际上
一是二者存储结构不同;
二就是就是浮点数精度的问题;

float 把 32 位分成了 3 部分,1 位(符号位)8 位(指数位)23 位(有效数字)那么 1+ 8 + 23 等于 32 吧,所以 float 的 32 位是这么来的。23 位有效数字就表示 float 真正能存的精度,23 位小数部分是反被储存的部分,所以它是有 24 位存储的,224(2 的 24 次方)=16777216
如果程序中有一个 float 的数值运算后的小数部分,如果超过 16777216.xxx 后运算的结果就会不准确;
double 则是 1 位符号位 +11 位指数 +52 位有效数字 = 64 位
有效数 253(2 的 53 次方)=9007199254740992,超过 9007199254740992.xxx 后运算的结果就会不准确;

浮点数计算结果不同的 CPU 计算出来可能是不一致的,像帧同步等就基本告别浮点数了,尽量用整型代替浮点型,实在需要可以用定点数,但也要注意是否存在定点数转回浮点数的现象;
其实还可以巧用位操作符,
位操作符比传统乘除效率要高,适合大量计算时使用
例如:
int a = 100 >> 1 相当于除 2 取整 结果为 50
int a = 100 << 2 相当于乘 4 取整 结果为 400
不过不必要过分追求位运算,在许多比较老的微处理器上, 位运算比加减运算略快, 通常位运算比乘除法运算要快很多,但现代架构中, 情况并非如此:位运算的运算速度通常与加法运算相同(仍然快于乘法运算)。在现代处理机架构中编译器一般会自动优化为移位运算的。还有很多芯片已经内置了硬件乘法器(乘法器的模型就是基于 “移位和相加” 的算法);

2.MonoBehaviour

1.MonoBehaviour 中,如果没有相应的事件要处理,要删除默认的空函数;
Update、FixedUpdate、LateUpdate 中,如果没必要每帧的逻辑,可以降低频率

Void Update(){
    ifTime.frameCount%6==0{
        //要执行的功能函数
    }
}

2.如果间隔更长,没必要每帧的逻辑,使用周期性的协程更妥当,例如使用 InvokeRepeating 函数:

InvokeRepeating();

3.Gameobject 不可见时,设置 enabled = false 时,update 就会停止调用。

  1. FindGameObjectWithTag 或者 GetComponent<>等查找操作放在 Star()先引用,不要放在 Update 或者 FixedUpdate 里;
  2. 协程使用 yield return new WaitForSeconds() 将会每帧导致 大概 21Byte GC,而 yield return null 会产生 大概 9 Byte GC;
    我们可以简单地通过复用一个全局的 WaitForEndOfFrame 对象来优化掉这个开销:

    static WaitForEndOfFrame EndOfTest = new WaitForEndOfFrame();
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(Test());
    }
    IEnumerator Test()
    {
        yield return null;
    
        while (true)
        {
           //原本是yield return new WaitForEndOfFrame();
            yield return EndOfTest;
    
        }
    }
    

    实际上实际上,所有继承自 YieldInstruction 的用于挂起协程的指令类型,都可以使用全局缓存来避免不必要的 GC 负担。常见的有:
    WaitForSeconds
    WaitForFixedUpdate
    WaitForEndOfFrame
    可以自己新建文件 xxx.cs 这个文件里,集中地创建了上面这些类型的静态对象,使用时可以直接这样:
    yield return xxx.GetWaitForSeconds(1.0f);

    6.使用内置数据代替新建数据
    例如: 使用 Vector3.up 而不是 new Vector(0, 0, 0);
    在这里插入图片描述
    7.缓存组件,缓存 Gameobject,调用 GetComponent 函数会有查找开销,用变量挂载到脚本使用,降低开销 (GetComponent 时如果获取到空的组件也会产生 GC)。在脚本挂载对象引用,可减少查找。
    8.查找对象标签用 if (go.CompareTag (“xxx”) 来代替 if (go.tag == “xxx”)。GameObject.tag 会在内部循环调用对象分配的标签属性,并分配额外的内存,并且效率也更低。
    9.使用委托机制代替 SendMessage,BroadcastMessage,SendMessageUpwards 这三个函数。
    因为它们的实现是一种伪监听者模式,利用的是反射机制,性能非常低。具体的性能大概是委托的十分之一。

3.Instantiate 实例化操作技巧

需要频繁实例化的 gameobject 比如子弹,需要放入对象池,可以大量减少实例化,销毁的开销。将部分耗时资源进行预载。

4.复杂的 UI 界面和带有动画组件的 GameObject 不要频繁切换 Active/Deactive

设置 Active/Deactive 复杂对象所用的耗时会比较大,这里可以使用一个小技巧,可以将需要 Deactive 的操作变成将 GameObject 移动到比较远的,摄像机之外。然后将 GameObject 上面的全部 Component 的 enabled 属性设置成 false。Active 时再重新设置回来

5.使用 for 或者 while 代替 foreach

foreach 每次调用会产生有 40Byte 左右的 GC 数据,产生 GC 的根本原因是使用了 using 语句。(GetEnumerator() 返回值类型,在
using 中装箱了)

6.合理使用数组,ArrayList,List

数组:内存中是连续存储的,索引速度非常快,赋值与修改元素也很简单。但不利于动态扩展以及移动。因为数组的缺点,就产生了 ArrayList。
ArrayList:使用该类时必须进行引用,同时继承了 IList 接口,提供了数据存储和检索,ArrayList 对象的大小动态伸缩,支持不同类型的结点。
ArrayList 虽然很完美,但结点类型是 Object,故不是类型安全的,也可能发生装箱和拆箱操作,带来很大的性能耗损。
List 是泛型接口,规避了 ArrayList 的两个问题。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册