Unity中的资源管理-使用Profile分析内存使用情况

2023-11-12

本文分享Unity中的资源管理-使用Profile分析内存使用情况

在上一篇文章中, 我们介绍了Ab的加载和使用, 并简单列举了其内存分布情况. 今天我们继续探索Ab的内存, 观察和实验其在各种阶段的分布情况.

Profile性能分析工具

在一切开始之前, 我们先简单介绍下Unity提供的性能分析工具: Profile.

Profile是Unity提供的一款性能分析工具, 与Editor一同发布, 我们可以在Window菜单下找到它, 不同版本的位置不同, 比如在Unity2017(Window->Profile), 而在Unity2019(Window->Analysis->Profile).

打开之后如图所示(本文基于Unity2019.4.26f1, 不同版本会有所差异, 但是大同小异):

在这里插入图片描述

我们关注左边的菜单:

  • CPU Stage: CPU使用情况
  • Rendering: 渲染情况
  • Memory: 内存情况

每个菜单点击之后, 下方的说明面板都有对应的概要情况说明.

注意本文只关注内存部分. 其它部分情况类似, 各位同学可以自行摸索, 传送门在此.

在选择内存菜单之后, 下方的说明面板可以有两种视图, 分别是: Simple(简单说明), Detailed(详情).

Simple视图展示Unity在每帧的实时内存信息概括, Unity会提前向系统申请预留内存, 以减少频繁的内存请求, 这个视图只是展示了各个类型的内存使用量, 而不涉及具体的细节.

展示的信息包括如下:

  • 第一行:
    • Used Total: 后续所有内存总和, 如上图:(Unity+Mono+GfxDriver+Audio+Video+Profile=341.6M)
    • Unity: Unity的原生代码使用的内存大小
    • Mono: 托管代码使用的内存大小
    • GfxDriver: 驱动程序对纹理, 渲染目标, 着色器和网格数据使用的内存估计量
    • Audio/Video: 音频和视频系统使用内存大小
    • Profiler: 性能分析器使用的总的内存大小
  • 第二行: 与第一行类似, 只是描述的是Unity向系统申请的预留内存.
  • 第三行: 整个系统所用内存大小, 与任务管理器中使用的大小一致, 根据该平台是否允许从系统获取内存情况, 这个值会显示不同的大小, 一般情况下都会大于上两行的总和, 因为Profile无法追踪所有的内存.
  • 更多行: 其余的信息一目了然, 分别是不同类型的资源所占内存大小, 还有游戏对象数量等基础信息, 这里不再赘述.

Detailed视图展示内存使用详情, 因为信息量巨大, 所以采用截图采样(或者说叫快照)的方式(Take Sample)截取某一帧进行分析, 同时, 勾选上中部的Deep Profile之后, 能够获取更多的信息, 启用Gather object references可以收集对象可能的引用信息, 如下图所示:
在这里插入图片描述

内存详情分为几个部分展示, 分别为:

  • Other: 资源, 游戏对象或组件之外的对象内存情况, 比如系统库, Profiler, 各种管理器等.
  • Not Saved: 标记为HideFlags.DontSave的对象, 即不保存到场景, 加载新场景也不会被销毁的对象, 是是 HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor | HideFlags.DontUnloadUnusedAsset的组合.
  • Assets: 从用户或者原生代码中引用的资源, 这是我们关注的重点部分.
  • Scene Memory: 当前场景的对象和其附加的资源
  • Builtin Resources: Unity Editor或者Unity内置资源

分析Ab内存占用

有了Profile的前置知识, 我们可以正式开始进行分析.

我们的目的是观察每个阶段, 内存的变化情况.

为了测试, 我们需要一个测试程序, 里面包含:

  • 加载Ab
  • 加载纹理(不需要实例化的资材)
  • 加载预制(需要实例化的资材)
  • 实例化对象
  • 摧毁对象
  • 卸载预制
  • 卸载纹理
  • 卸载Ab, 且不摧毁资材和对象
  • 卸载Ab, 摧毁资材和对象

每个阶段对应一个按钮和回调, 效果如下:

在这里插入图片描述

Controller对象删挂载控制脚本: ResourcesTest

public class ResourcesTest : MonoBehaviour {
    public Button btnClear;
    public Button btnLoadAb;
    public Button btnLoadTexture;
    public Button btnLoadPrefab;
    public Button btnInstanceObj;
    public Button btnDestroyObj;
    public Button btnUnloadPrefab;
    public Button btnUnloadTexture;
    public Button btnUnloadAb;
    public Button btnUnloadAbAndDestroy;

    private AssetBundle m_Ab;
    private GameObject m_Prefab;
    private GameObject m_Obj;
    private Texture m_Texture;
    
    void Start() {
        btnClear.onClick.AddListener(Clear);
        btnLoadAb.onClick.AddListener(LoadAb);
        btnLoadTexture.onClick.AddListener(LoadTexture);
        btnLoadPrefab.onClick.AddListener(LoadPrefab);
        btnInstanceObj.onClick.AddListener(InstanceObj);
        btnDestroyObj.onClick.AddListener(DestroyObj);
        btnUnloadPrefab.onClick.AddListener(UnloadPrefab);
        btnUnloadTexture.onClick.AddListener(UnloadTexture);
        btnUnloadAb.onClick.AddListener(() => UnloadAb(false));
        btnUnloadAbAndDestroy.onClick.AddListener(() => UnloadAb(true));
    }

    void Clear() {
        Resources.UnloadUnusedAssets();
    }
    
    void LoadAb() {
        UnloadAb(true);
        
        var ab = AssetBundle.LoadFromFile("Assets/Output/AssetBundle/AllAb/prefabs");
        m_Ab = ab;
        
        Assert.IsNotNull(m_Ab);
    }

    void LoadTexture() {
        m_Texture = m_Ab.LoadAsset<Texture>("Common_Logo");
        Assert.IsNotNull(m_Texture);
    }
    
    void LoadPrefab() {
        m_Prefab = m_Ab.LoadAsset<GameObject>("Attack");
        Assert.IsNotNull(m_Prefab);
    }
    
    void InstanceObj() {
        m_Obj = Instantiate(m_Prefab);
        Assert.IsNotNull(m_Obj);
    }
    
    void DestroyObj() {
        if (m_Obj != null) {
            Destroy(m_Obj);
            m_Obj = null;
        }
    }

    void UnloadPrefab() {
        m_Prefab = null;
        Resources.UnloadUnusedAssets();
    }
    
    void UnloadTexture() {
        Resources.UnloadAsset(m_Texture);
        m_Texture = null;
    }
    
    void UnloadAb(bool needDestroy = false) {
        if (m_Ab != null) {
            m_Ab.Unload(needDestroy);
            m_Ab = null;
        }
    }

    private void OnDestroy() {
        UnloadAb(true);
    }
}

分阶段进行测试

初始情况下, 在启动之后, 我们先清理所有无用资源(点击清理按钮)并记录一个内存快照:

在这里插入图片描述

加载和卸载Ab

点击加载Ab按钮之后:

在这里插入图片描述

我们看到, OtherNot Saved数量增加了一个, 展开后经过一番比较之后, 发现增加的是:

在这里插入图片描述
在这里插入图片描述

因为Ab本身是一个序列化文件, 所以在Other中的SerializedFile增加一个对象. 同时在Not SavedAssetbundle增加了该Ab.

点击卸载Ab或者卸载Ab(不摧毁资材和对象)之后, 内存恢复. 因为这里还没有加载任何资材或者实例化对象, 所有两种卸载方式表现一致.

加载和卸载纹理

因为在Editor下, 每次启动后会有细微的差别, 所以每个阶段我们都从加载Ab开始.

重新启动并清理之后:
在这里插入图片描述

加载Ab之后:
在这里插入图片描述

点击加载纹理:

在这里插入图片描述

OtherNot Saved没有变化, Assets数量增加了2, 也就是纹理对应的纹理资源和精灵(测试的资源为精灵).

如果现在点清理按钮, 内存不会有变化, 因为Texture被本地变量引用着, 不会被清理.

点击卸载Ab(不摧毁资材和对象)按钮后, Ab被卸载, 但是纹理依然存在.

在这里插入图片描述

清理并且重新加载Ab和纹理之后, 点击卸载Ab按钮后, Ab被卸载, 且纹理也被卸载, 恢复到加载Ab之前的状态.

清理并且重新加载Ab和纹理之后, 点击卸载纹理按钮后, 纹理被卸载, 恢复到加载纹理之前的状态.

加载和卸载预制

预制是一种需要实例化后使用的资材, 且因为其一般包含很多对其它资材的引用, 所以一旦加载预制, 会同时将其所有引用都加载到内存.

重新启动并清理之后:

在这里插入图片描述

加载Ab之后:

在这里插入图片描述

点击加载预制按钮后:

在这里插入图片描述

Assets整整增加了41个, 因为该预制引用的资材比较多, 有纹理, 有对象, 有材质,有shader等.

点击卸载预制之后按钮后, Assets恢复原始大小, 但是Other却增大了, 作者细致比较后也没有发现增大的部分在哪, 希望了解的同学在评论区告知.

重新加载预制后, 点击卸载Ab(不摧毁资材和对象)按钮后, 预制和其引用资源依然存在, Ab被卸载.

重新加载预制后, 点击卸载Ab按钮后, 预制和其资源与Ab一同被卸载.

加载和卸载预制加实例化对象

重新启动, 清理, 加载Ab和预制之后:

在这里插入图片描述
在这里插入图片描述

Not SavedScene Memory有所变化.

Not Saved增加了2, 来自于两个阴影:

在这里插入图片描述

Scene Memory增加了22, 来自于对象引用的各种资材和实例化对象:
在这里插入图片描述

点击摧毁对象按钮后, Not SavedScene Memory恢复了大小.

重新加载预制后, 点击卸载Ab(不摧毁资材和对象)按钮后, 预制, 其引用资源和实例化对象依然存在, Ab被卸载.

重新加载预制后, 点击卸载Ab按钮后, 其引用资源和实例化对象与Ab一同被卸载. 但是对象的变量引用没有移除, 所以Scene Memory管理的部分无法被清理, Scene Memory的大小无法恢复.

总之, 在卸载Ab时, 如果同时要销毁加载的资材和实例化的资源, 需要注意清理所有引用, 不然可能会有严重的问题.

总结

今天介绍了Unity的性能分析工具: Profile, 并提供了一个简单的例子用于分析Ab各个阶段, 各种情况下的内存情况.

因为整个分析过程是一个动态的过程, 很难使用文章清晰的表达, 导致很多内容没办法介绍清楚.

动态分析类的文章比较干, 能写清楚的就更少了, 视频介绍更方便一些, 如果有机会的话再给大家录制.

感兴趣的同学可以基于今天的内容, 自行构建测试用例来分析, 想必一定会有自己的收获.

下一阶段将进入本专栏最后一部分的内容: 一套商业化资源管理方案.

年底了, 公司业务繁忙, 希望能在过年之前更新完毕, 实在没时间的话只能等明年了.

好了, 今天的内容就这么多, 为了这篇文章, 作者的头发都少了几根, 希望对大家有所帮助哈!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unity中的资源管理-使用Profile分析内存使用情况 的相关文章

  • UE4 解决景深效果闪烁问题

    原因 1 模型的垂直竖线 造成抗锯齿算法对竖线的渲染计算 处于一种不稳定的状态 因此闪烁 解决办法 使用LOD 用贴图去替代线条模型 2 材质的法线贴图 当法线贴图含有垂直竖线的纹理效果 也会造成闪烁 比如这种幕墙材质 解决办法 关闭或动态
  • Unity 粒子特效、材质发光 HDR ShaderGraph图文教程[完成lit发光设置]

    效果如图 准备工作 在hdr模式下 关闭Directional Light 相机设置 移动球挂一个点光源作为子节点 设置自行调节 0 创建移动球的材质及shader shader gt 在Project Create Shader Grap
  • Unity-AR 简介

    Unity AR 简介 现有Unity AR Sdk ARKit 苹果推出的AR开发平台 ARCore Google 推出的增强现实 SDK ARFoundation ARFoundation是ARKit XR插件和ARCore XR插件
  • Unity动画控制器animator.CrossFade

    需要特别注意 1 CrossFade虽然可以不用任何逻辑来链接而直接跳转 但是CrossFade只能覆盖其他动画 当当前动画播放完毕而没有跳出这个动画时再次调用CrossFade将会失败 造成动画依旧停在原位 参数animator Cros
  • Unity3d 插件 系列——DoTweenPro介绍(图文详细+案例)

    Unity3d 插件 系列 DoTweenPro介绍 图文详细 案例 前言 一 DoTweenPro简介 二 DoTweenPro安装 三 DoTweenPro主要组件 1 DoTweenAnimation 2 DoTweenPath 3
  • GooglePlay提审警告(com.google.android.gms:play-services-safetynet:17.0.0)

    1 Goole在今年6月份出的新政策 不在使用safetynet 而使用Play Integrity API 2 项目本身没有使用过safetynet 3 使用了firebase 查阅资料 解决方案如下 implementation pla
  • Unity打开工程时卡住的问题

    自从Unity升级了一个版本后 Unity打开工程卡住的问题越来越严重了 具体表现为 选择工程后 Unity窗口消失 但进程还在 有时候等个几分钟能出来 有时候等10分钟都不见得能出来 直观感受上看 似乎是Unity加载工程的时候某一步卡了
  • unity: C#的Action Event Delegate的异同

    目录 一 Action 二 Event 三 Action和Event区别 四 Delegate 总结 Action Event Delegate的异同 前言 Action Event和Delegate都是C 语言中的重要概念 分别用于管理函
  • VLC for unity 插件如何使用

    VLC for unity 插件如何使用 先去下载一个VLC播放器 安装完成后 然后导入插件链接https download csdn net my 这个插件我的另一个上传资源里有 或者到商店去下载 这个插件链接下载完是一个txt文档 里面
  • Unity保存图片到相册

    Unity保存图片到Android相册 Java 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
  • unity 性能查看工具Profiler

    文章目录 前言 profiler工具介绍 菜单栏 帧视图 模块视图 模块详细信息 通过profiler分析优化游戏性能 最后 前言 每次进行游戏优化的时候都用这个工具查看内存泄漏啊 代码优化啊之类的东西 真的好用 但是之前也就是自己摸索一下
  • unity小球跟随音乐节奏放大缩小和改变颜色

    放在小球身上 设置对应组件即可 using System Collections using System Collections Generic using Unity VisualScripting using UnityEngine
  • 【Unity】如何让Unity程序一打开就运行命令行命令

    背景 Unity程序有时依赖于某些服务去实现一些功能 此时可能需要类似打开程序就自动运行Windows命令行命令的功能 方法 using UnityEngine using System Diagnostics using System T
  • 【Unity】运行时创建曲线(贝塞尔的运用)

    Unity 运行时创建线 贝塞尔的运用 1 实现的目标 在运行状态下创建一条可以使用贝塞尔方法实时编辑的网格曲线 2 原理介绍 2 1 曲线的创建 unity建立网格曲线可以参考 Unity程序化网格体 的实现方法 主要分为顶点 三角面 U
  • Unity学习笔记

    一 旋转欧拉角 四元数 Vector3 rotate new Vector3 0 30 0 Quaternion quaternion Quaternion identity quaternion Quaternion Euler rota
  • Unity学习笔记

    一 旋转欧拉角 四元数 Vector3 rotate new Vector3 0 30 0 Quaternion quaternion Quaternion identity quaternion Quaternion Euler rota
  • U3D游戏开发中摇杆的制作(UGUI版)

    在PC端模拟摇杆 实现玩家通过控制摇杆让玩家移动 以下是完整代码 using System Collections using System Collections Generic using UnityEngine using Unity
  • 游戏开发常见操作梳理之NPC任务系统

    多数游戏存在任务系统 接下来介绍通过NPC触发任务的游戏制作代码 using System Collections using System Collections Generic using UnityEngine
  • 游戏开发常见操作梳理之NPC药品商店系统(NGUI版)

    后续会出UGUI Json的版本 敬请期待 游戏开发中经常会出现药品商店 实际操作与武器商店类似 甚至根据实际情况可以简化设置 废话不多说 直接上代码 药品商店的源码 using System Collections using Syste
  • 游戏开发之常见操作梳理——武器装备商店系统(NGUI版)

    游戏开发中经常出现武器商店 接下来为你们带来武器装备商店系统的具体解决办法 后续出UGUI Json版本 敬请期待 武器道具的具体逻辑 using System Collections using System Collections Ge

随机推荐