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 分块延迟渲染01 (TBDR)

    现代移动端图形体系结构的概述 现代SoC通常会同时集成CPU和GPU CPU被用于处理需要低内存延迟的序列 大量分支的数据集 其晶体管用于流控制和数据缓存 GPU为处理大型 未分支的数据集 如3D渲染 晶体管专用于寄存器和算术逻辑单元 而不
  • 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插件
  • UnityVR--组件3--Line Renderer--线性渲染

    目录 线性渲染组件简介 绘制线条Line Renderer组件介绍 绘制拖尾Trail Renderer组件介绍 应用1 使用Line Renderer绘制线段 应用1实现 使用系统工具或自定义工具绘制线段 应用2 Trail Render
  • Unity与Android的Back键冲突解决

    Unity与Android的Back键冲突解决 上一篇的最后留下了两个问题 Unity视图下横屏闪退 Unity视图下Android无法响应back返回上一activity 对于第一个问题 应该是Unity横屏下视图的某些设置跟Androi
  • unity工程崩溃资源找回

    1 Unity死机未保存场景 当你在Unity中编辑场景 突然死机时 可以在项目文件目录中找到Temp文件夹 双击文件夹 找到 Backupscenes文件夹 把后缀为 backup的文件后缀改为 unity 然后拖进Unity的Proje
  • unity 性能查看工具Profiler

    文章目录 前言 profiler工具介绍 菜单栏 帧视图 模块视图 模块详细信息 通过profiler分析优化游戏性能 最后 前言 每次进行游戏优化的时候都用这个工具查看内存泄漏啊 代码优化啊之类的东西 真的好用 但是之前也就是自己摸索一下
  • Unity旋转以及万向锁问题

    我之前研读了一些关于unity旋转相关的博客 一直想抽个时间写个总结 但是由于实习太忙一直没有写 趁着今天请了假晚上有时间把这段时间一些学习心得写出来 Unity inspector面板中的Rotation 在unity中 想必大家最先接触
  • unity dots jobSystem 记录

    Looking for a way to get started writing safe multithreaded code Learn the principles behind our Job System and how it w
  • unity3d 自定义的图片无法放入source image中

    须将图片的texture type改为 sprite
  • 【原神游戏开发日志1】缘起

    原神游戏开发日志1 缘起 版权声明 本文为 优梦创客 原创文章 您可以自由转载 但必须加入完整的版权声明 文章内容不得删减 修改 演绎 相关学习资源见文末 大家好 最近看到原神在TGA上频频获奖 作为一个14年经验的游戏开发行业的老兵 我就
  • mixamo根动画导入UE5问题:滑铲

    最近想做一个跑酷游戏 从mixamo下载滑铲动作后 出了很多动画的问题 花了两周时间 终于是把所有的问题基本上都解决了 常见问题 1 动画序列 人物不移动 2 动画序列 人物移动朝向错误 3 蒙太奇 人物移动后会被拉回 4 蒙太奇 动画移动
  • 【转载】【Unity】WebSocket通信

    1 前言 Unity客户端常用的与服务器通信的方式有socket http webSocket 本文主要实现一个简单的WebSocket通信案例 包含客户端 服务器 实现了两端的通信以及客户端向服务器发送关闭连接请求的功能 实现上没有使用U
  • 【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
  • U3D游戏开发中摇杆的制作(UGUI版)

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

    游戏中一般存在小地图系统 实际上就是设置一个新的摄像机放置在玩家的正上方 然后在小地图上显示新摄像机看见的东西就可以了 在小地图上一般存在放大地图和缩小地图的按钮可以方便放大和缩小地图 这些操作是如何实现的呢 接下来直接上核心代码 usin
  • 游戏开发之常见操作梳理——武器装备商店系统(NGUI版)

    游戏开发中经常出现武器商店 接下来为你们带来武器装备商店系统的具体解决办法 后续出UGUI Json版本 敬请期待 武器道具的具体逻辑 using System Collections using System Collections Ge
  • 游戏开发常用实践操作之按动任意键触发

    接下来一些笔记会对于一些大大小小的实践操作进行记录 希望对你有所帮助 在游戏中 我们经常会遇到一些按动任意键触发的操作 接下来展示核心代码 以下是对于Unity中的操作 使用的UI是NGUI 对于核心操作没有影响 你可以自己置换 void

随机推荐