浅谈-Unity内存管理

news2024/11/15 3:40:09

灵魂拷问-什么是内存

  1. 物理内存
  2. 虚拟内存
  3. 内存寻址方位

物理内存

下面是一张i7的处理器的芯片细节图,在整个板载面积上我们可以很明显的看到Shared L3 Cache占用了最大面积。为什么?因为硬件产商为了让我们忽略掉CPU访问内存是一个非常慢速的过程,所以CUP在访问内存的时候会有很多板载的Cahe,对于一个芯片来讲它的板上面积是非常珍贵的,但是从下面图中我们可以看到真正用到计算的板块区域大小是非常少的(6个Core区域)。剩下的大部分珍贵面积都被用在了IO和内存相关的工作上。

图-1
Cache 是如何工作的

现代CPU的运算频率相较于服务器主内存的访问速度存在较大的差异,为了解决访问主存速度过慢的问题,现代CPU中一般会增加多个层次的cache,CPU cache使用SRAM制造且离CPU计算单元的距离更近,因此有着更快的访问速度。

有了CPU cache之后,CPU处理数据时首先尝试从cache中读取,如果找到就可以立即送入CPU处理,如果未找到,则会从下一层级的cache或者主存中读取,同时把读取的数据放入cache以便下一次使用. 现代CPU的一般使用三层cache(L1/L2/L3),其中L1又分为指令cache和数据cache,其架构大致如下图所示:

从L1 cach 到 L2 cache 到 L3 cache,容量越来越大,离CPU计算核心越来越远,而速度则越来越慢。

总结就是CPU访问内存是一个非常慢的过程,由于数据在不同层级cache以及主寸之间的转移是以cache line(64Bytes)为单位, 而从不同层级cache的速度差异可以看出,为了达到更快的数据访问速度,我们需要尽量发挥cache的局部性特征,将计算时序上相邻的数据在内存地址上安排在一起,目标是实现一次读取到高层次cache的数据即可供后续的多次运算使用。任何想达到最优计算性能的内存数据结构都需要遵循这个设计思想。

Unity ECS and DOTS 

Unity DOTS设计的初衷就是将数据不连续的状态变为数据连续状态,这样CUP在读取内存的时候,下一次的Cache Miss就会降低,从而提高CPU有效的访问时间

台式设备与移动设备内存架构差异
  • 没有独立显卡
  • 没有独立显存
  • CPU板上面积更小,缓存级数更少,大小更小(主流台式CPU L3Cache 在 8M到16M 而手机CPU L3Cache 2M,差了4-8倍)

虚拟内存

 内存在计算机中的作用很大,电脑中所有运行的程序都需要经过内存来执行,如果执行的程序很大或很多,就会导致内存消耗殆尽。为了解决这个问题,Windows中运用了虚拟内存技术,即拿出一部分硬盘空间来充当内存使用,当内存占用完时,电脑就会自动调用硬盘来充当内存,以缓解内存的紧张。举一个例子来说,如果电脑只有128MB物理内存的话,当读取一个容量为200MB的文件时,就必须要用到比较大的虚拟内存,文件被内存读取之后就会先储存到虚拟内存,等待内存把文件全部储存到虚拟内存之后,跟着就会把虚拟内里储存的文件释放到原来的安装目录里了。

交换内存

当操作系统在使用内存不够的情况下,会尝试把一些不用的内存交换到硬盘上,从而节省出更多的物理内存,来提供给系统比较活跃的线程或者应用。

移动设备不支持内存交换
ios可以进行内存压缩

把不活跃的内存压缩到内存的特定空间里,节省出物理内存供活跃的App使用

Android没有内存压缩能力

内存寻址范围

如图1中 的 Memory Controller 的区域,通常来说64位CPU比32位CPU的内存寻址范围要大,当然不是一定的,因为这里的64位与32位讲的是运算位数,并不是内存范围。

Andriod 内存管理

内存基本单位-Page

andriod 是基于linux的操作系统,最基本的管理单位是一个Page,默认情况下

  • 一个Page是4K
  • 回收和分配以page为单位
  • 用户态和内核态(内核态的内存用户态是不可以访问的)

内存杀手-low memory killer

当前台App内存使用量达到一定程度上,会导致后台app关闭了,或者当前app直接闪退了,更严重可能直接重启桌面或者手机重启了。这都是因为 memory killer干的,当系统内存不够后,killer会如上图中的 从最底下一层一层往上杀 

 Cached->Previous->Home->....->System 杀到System的时候手机就重启了

内存指标

  • Resident Set Size
  • Proportional Set Size
  • Unique Set Size

RSS(Resident Set Size):是当前App所应用掉的所有内存,如下Rss图app调用了Google Play Services的4个page内存,在RSS统计的使用这部分内存会统计到App身上

PSS(Proportional Set Size):App 按比例统计,比如PSS图所示两个进程共享,那就负责一半page。如果三个进程共享,那就负责三分之一

USS(Unique Set Size):只有统计自己使用的内存,公共使用的内存不算进来

Rss
PSS
USS

总结:如果pss很高,uss很低,那说明app调用了一个非常大的公共库,内存公摊了。实际工作中能做到的是USS上的优化,以及避免在pss上照成更大的压力。

以上讲了一段内存相关的知识点,接下来我们回归到Unity上来讲:

Unity 是一个C++引擎

  • 底层代码完全由C++写成 
  • 通过wrapper提供给用户API
  • 用过代码会转换为cpp代码(il2cpp)
  • vm任然存在(为了跨平台)

Unity内存按照分配方式分别为:

  • Native Memory
  • Managed Memory
  • Editor & Runtime

一个Asset在runtime的使用不去load它的时候它是不会进内存的,如果是在Editor模式下,你打开它,它就进内存了,所以unity编辑器会在一开始加载它序列化它,第一次开项目会话很长的时间。这样的优点是在你开发的过程中更流畅。但是头一次要花费更长的时间。

Unity内存按照管理者分为:

  • 引擎管理内存
  • 用户管理内存

Unity 检测不到的内存

  • 用户分配的native内存(c++插件)
  • lua内存

Unity Native Memory管理

  • Allocator与memory lable
  • NewAsRoot
  • GetRuntimeMemory
  • 会及时返还给系统

unity重载了c++分配内存的操作符(allocator,new),在使用的时候要求多一个参数memory lable,指的是当前这块内存要分配到哪一个内存池里。

比如一个shader,当我们加载进内存的时候会生成一个shader的root(NewAsRoot),然后在shader底下会有很多的数据比如sub shader,pass等等,这些数据就会作为root的成员去依次分配。

什么会导致Native内存增长?

Scene

所有的实体都会反应到C++上,因为一个GameObject,在c++层上会存在一个或多个对象(gameobject身上的component)来存储信息。如果scene上有过多的GameObject,那么Native内存就会上升

Audio

DSP buffer 声音缓冲

当一个声音要播放的时候会发送一个cpu指令说要播放声音,当一个声音的数据量非常小,就会频繁的发送指令就会频繁的发声IO,DSP buffer 就是为了解决频繁发送的问题,它会等待填充慢了才会向cpu发送指令。当然也是有缺点的,如果你设置的 dsp buffer 过大会导致声音的延迟,如果设置过小,就会导致cpu负担上升。

Froce to mono

有的音频会存在双声道,但是有很多单声道的声音倍设置成了双声道,这就意味着一个 1M的声音,会变成2M的内存,这2M不仅在包体里,也会在内存里。所以单声道的声音就设置成单声道即可。

Format

不同平台会有不同的format支持,ios一般选 MP3,android并没有硬件支持都可以 wav,ogg

Compression Format

声音在内存里的格式:压缩的,流,解压的

Code Size

模板泛型的乱用,每个不同的泛型的组合会倍编译成不同的类,假设A类有三个泛型第一个泛型你使用了int,string,float 就会变编译成3个类,如果你每个泛型的组合都使用了就会被编译成3*3*3=27种可能,包体会增大,代码一样类型却不一样。

public class A<T,T1,T2>{...}
// 编译后
public class A1<int,int,int>{...}
public class A2<string,int,int>{...}
public class A3<float,int,int>{...}
......

AssetBundle

TypeTree

TypeTree 是Unity做版本升级使用的,unity迭代过程每一种类型都有可能做数据结构的改变,为了做数据结构改变的兼容,它会在生成这种数据类型的序列化的时候对应的生成TypeTree,当前版本所用到了哪些变量类型是什么,会通过TypeTree做反序列化。如果上一个版本类型在当前版本中没有就不做处理,如果当前版本出现了一个新的类型,那么就会使用默认。保证了在不同版本之间导致序列化出错。

在Build Bundle的时候如果关闭掉Type Tree生成,就会减少包体,当然要确保你当前AssetBundle使用的unity版本,和你构建时的unity版本是一致的就可以关闭掉。

关闭TypeTree有三个好处

  • 包体变小
  • runtime会变快 (因为会节省TypeTree的序列化)
  • 内存减少

Lz4

压缩方式Lz4是主推的,Lz4的压缩速度是Lzma的10倍,但是它的压缩比率比Lzma差30%

Lzma

Lzma 压缩速度和读取速度都比Lz4慢,而且会占大量的内存,因为它需要一次全解压出来,不是一块一块的解压。

Size &  Count

 一个AssetBundle打多大合适?assetbundle不能过小,因为assetbundle会有自己的头数据,asset过小打了assetbundle可能会导致比不打还大,内存被消耗在无意义的头上了。所以不要太大也不要太小,官方给出合适的大小是2M,当然它们是考虑了网络带宽,现在是5G时代了可以更大点,当然要实际考虑用户带宽的承受能力,和对下载的敏感程度。

Resources

resources构建的时候会生成一个红黑树 R-B-Tree,用来检索asset在那个地方,而且是不可卸载的,所以在开始加载游戏的时候就会去解析RBTree(影响游戏启动时间),并且内存一直占用着,所以现在基本没人使用。

Texture

  • upload buffer (和上面讲的一致,填满多大向GPU push)
  • r/w (尽量不开,开了之后内存和显存个占用一份内存,否则是共用一份)
  • Mip Maps (UI就没必要开了)

Mesh

  • r/w  (尽量不开,开了之后内存和显存个占用一份内存,否则是共用一份)
  • compression

Assets

托管部分内存

VM内存池
  • Mono VM
  • IL2CPP VM

VM会把内存返还给OS吗?

返还的条件是什么?

当一快内存连续6次GC没被访问到时,会返还给OS

GC机制

  • Throughput(回收能力)
  • Puase  times(碎片化)
  • Fragmentation(碎片化)
  • Mutator overhead(额外消耗)
  • Scalability(可扩展性)
  • Portability(可移植性)

Unity 使用的是Boehm,Non-generational(非分代式)

分代是指:大块内存、小内存、超小内存是分在不同内存区域来进行管理的。还有长久内存,当有一个内存很久没动的时候会移到长久内存区域中,从而省出内存给更频繁分配的内存。

并且是 Non-compacting(非压缩式) ,如果是分代的话,当有内存被回收的时候,压缩内存会把下图空的地方重新排布。但 Unity 的 BOEHM 不会!它是非压缩式的。空着就空着,下次要用了再填进去。

历史原因:Unity 和 Mono 合作上,Mono 并不是一直开源免费的,因此 Unity 选择不升级 Mono,与实际 Mono 版本有差距。

新一代 GC(Incremental garbage collection - Unity 手册)

Incremental GC(渐进式 GC):
1.现在如果我们要进行一次 GC,主线程被迫要停下来,遍历所有 GC Memory ,来决定哪些 GC 可以回收。
2.Incremental GC 把暂停主线程的事分帧做了。一点一点分析,主线程不会有峰值。总体 GC 时间不变,但会改善 GC 对主线程的卡顿影响。

SGen 或者升级 Boehm?

SGen 是分代的,能避免内存碎片化问题,调动策略,速度较快

IL2CPP:现在 IL2CPP 的 GC 机制是 Unity 自己重新写的,是升级版的 Boehm

Memory fragmentation 内存碎片化

 为什么内存下降了,但总体内存池还是上升了?

  • 因为内存太大了,内存池没地方放它,虽然有很多内存可用。(内存已被严重碎片化)
  • 当开发者大量加载小内存,使用释放*N,例如配置表、巨大数组,GC 会涨一大截。
  • 建议先操作大内存,再操作小内存,以保证内存以最大效率被重复利用。
Zombie Memory(僵尸内存)

内存泄露说法是不对的,内存只是没有任何人能够管理到,但实际上内存没有被泄露,一直在内存池中,被 zombie 掉了,这种叫 Zombie 内存。

  • 无用内存:
    1.Coding 时候或者团队配合的时候有问题,加载了一个东西进来,结果从头到尾只用了一次。
    2.有些开发者写了队列调度策略,但是策略写的不好,导致一些他觉得会被释放的东西,没有被释放掉。
    3.找是否有活跃度实际上并不高的内存。
  • 没有被释放的内存

内存实践-Managed内存

用destory,别用Null。

Class VS Struct

Pool In Pool (池中池)

Closures and anonymous methods (闭包和匿名函数)

所有的匿名函数和闭包都会被生成一个为class,只是匿名的class,闭包或者函数单中的所有数据都会变成class的属性占用内存,所以需要慎用

Coroutines(协程)

协程在没有停止的时候,即使是局部变量所有的数据都会占用的内存,因为协程整个函数没有返还所以一直占用着,最好的方式就是用完就停止,因为协程实际上是Update轮询的。

Configuretion (配置表)

Singleton (慎用单例会一直存在整个app生命周期中)

UI 的 SetActive(会做很多的设置和初始化的工作以及递归子物体,如果SetAtive照成卡顿可以将UI移到屏幕外面就不会进行渲染)

性能优化UPR 工具

Unity Live Help

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2096128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx: TCP建立连接的优化和启用Fast Open功能

TCP 建立连接优化 在三次握手中&#xff0c;相关TCP的内核参数可优化这一过程 net.ipv4.tcp_syn_retries 6net.ipv4.tcp_synack_retries 5net.ipv4.tcp_syncookies 0net.ipv4.tcp_max_syn_backlognet.core.somaxconnnet.core.netdev_max_backlog 1 &#xff09; net.ipv4…

游戏:科技强国的璀璨星芒与经济增长新动力

游戏&#xff1a;科技强国的璀璨星芒与经济增长新动力 在时代的浪潮中&#xff0c;游戏正以一种令人瞩目的姿态&#xff0c;成为科技强国之路上一颗闪耀的星&#xff0c;同时也对经济有着多方面的深远影响。 从《黑神话&#xff1a;悟空》的爆火&#xff0c;到美国、英国、法国…

磐石云AXB小号平台同时支持AXYB、AXN、AXYBN

外卖订单&#xff0c;物流配送&#xff0c;金融&#xff0c;房地产&#xff0c;等行业都在使用订单小号或者说是工作号。 在当今数字化信息爆炸的时代&#xff0c;通信方式的多样化和复杂化给我们带来了便利&#xff0c;但也带来了管理的挑战。面对繁杂的通信需求&#xff0c;…

Ai Illustrator 取消吸附到像素点,鼠标拖动的时候只能到像素点

Ai Illustrator 取消吸附到像素点&#xff0c;鼠标拖动的时候只能到像素点 在做图的时候无意间变成吸附到像素点了&#xff0c;导致无法更细致的移动点。 像这样&#xff1a; 关闭的方法是打开上面菜单中的 【视图】取消勾选【对齐像素】 即可。 结果就是&#xff1a;

C++实现彩虹猫时空隧道特效(无害)

#include <Windows.h> // 如果不是在Visual Studio环境下运行的话W最好改小写。 using namespace std;int main() {for (int i 1; i < 10; i) {HDC hdc GetWindowDC(GetDesktopWindow());RECT rect;GetWindowRect(GetDesktopWindow(), &rect);StretchBlt(hdc, r…

240901-通过端口转发在局域网内访问WLS2中Gradio的Web应用

A. 需求描述 两台不同的电脑共用同一个路由器&#xff0c;其中一台电脑时Windows&#xff0c;上面安装了WSL2&#xff0c;我在WLS2中启动了一个Gradio的网络应用&#xff0c;并设置了server_name‘0.0.0.0’&#xff0c;另外一台电脑如何访问该网络应用。 B. 方法介绍 要使另一…

STM32H750+CubeIDE+FreeRTOS+ETH(LAN8720A)+LWIP

文章目录 STM32H750CubeIDEFreeRTOSETH(LAN8720A)LWIPCubeIDE配置RCC时钟树SYSETH串口MPUFreeRTOSLWIPGPIO然后就可以点击生成代码了&#xff01; 代码修改printf重定向补充硬件复位更改补充链接文件然后就可以编译下载ping成功了&#xff01; socket网络编程 STM32H750CubeIDE…

Freepik发布号称目前最强AI图像生成器Mystic

Freepik 于2024年8月27日正式推出了新的人工智能图像模型 “Mystic”&#xff0c;并号称是目前最强的AI图像生成器&#xff0c;本文从介绍Mystic开始&#xff0c;剖析其技术细节&#xff0c;功能特色&#xff0c;收费价格&#xff0c;并与当前市场上领先的 Midjourney V6.1 进行…

【dotnet】vscode配置dotnet开发环境

下载dotnet sdk https://dotnet.microsoft.com/en-us/download/visual-studio-sdks下载完安装&#xff0c;安装完毕在cmd中输入以下代码 dotnet出现以下结果代表安装完毕 在vscode终端中输入以下创建web项目 dotnet new webapp --output aspnetcoreapp --no-https上述命令…

[Jsprit]Jsprit学习笔记-vrp问题的求解

目录 一、整体的求解逻辑主要步骤 二、搜索策略的选择三、搜索策略执行解1、解的选择2、解的破坏3、解的接受3.1 新解的接受策略 一、整体的求解逻辑 下面是Jsprit实现的代码部分 public Collection<VehicleRoutingProblemSolution> searchSolutions() {logger.info(&q…

基于3D Slicer与matlab平台的图像引导介入手术-demo

1.实现手术手术导航的基本框架 2、基本协议框架 3、演示视频 DemoWithChinse2

unordered系列容器的实现

1. unordered_set与unordered_map的结构 我们知道STL中的unordered_set与unordered_map底层就是一个开散列的哈希表 1.1 unordered_set的结构 我们知道unordered_set其实就是K模型&#xff0c;所以unordered_set容器对红黑树的封装如下&#xff1a; template<class k, cl…

VTK随笔十二:体绘制(体绘制管线、vtkVolumeMapper、vtkVolume、不规则网格数据体绘制技术 )

体绘制&#xff0c;有时又称作三维重建(区别于投影图像的三维重建)&#xff0c;是一种直接利用体数据来生成二维图像的绘制技术。与面绘制不同&#xff0c;体绘制技术不需要提取体数据内部的等值面&#xff0c;它是一个对三维体数据进行采样和合成的过程。体绘制能够通过设置不…

【深度学习入门】计算机视觉任务

一、引言 对于神经网络&#xff0c;可以把中间的隐藏层看作一个黑盒子&#xff0c;这个黑盒子能自动选择如何提取特征&#xff0c;这不同于传统机器学习的人工操作&#xff0c;它的实现原理也是我们学习深度学习的重点。本文章以计算机视觉任务中的图像分类任务为例子&#xff…

zookeeper 集群搭建 及启动关闭脚本

1准备奇数台机子3&#xff0c;5&#xff0c;7 我准备的是三台 192.168.58.81 zookeeper-1 192.168.58.82 zookeeper-2 192.168.58.83 zookeeper-3 下载jdk 把他配置环境变量并检查是否是环境变量 echo $JAVA_HOME cd /opt/software wget http://mirrors.hust.edu.…

docker部署project-exam-system项目

8月30日笔记 项目实战&#xff1a;使用docker部署project-exam-system 1、背景&#xff1a; 使用基础的docker指令来创建镜像&#xff0c;实现项目的发布&#xff0c;使用Dockderfile&#xff0c;docker compose编排容器。 2、环境准备&#xff1a; &#xff08;1&#x…

sts 0/1 没有 pod生成 -> kube-controller-manager没了

kube-controller-manager.yaml 在 nerdctl ps -a 看不到 journalctl -xu kubelet > /tmp/kubelet.log /tmp/kubelet.log 老6

OpenSetting组件的用法

文章目录 1. 概念介绍2. 使用方法与主要功能2.1 使用方法2.2 主要功能 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何获取App自身信息"相关的内容&#xff0c;本章回中将介绍一个三方包:open_setting.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念…

JavaWeb:实验二JSP表单开发及访问数据库

一、实验目的 1&#xff0e;掌握JSP表单的开发方法。 2&#xff0e;熟悉JDBC技术和使用JDBC连接各种数据库。 二、实验性质 综合性实验 三、实验内容 实现注册与登录功能&#xff1a; 1.创建一个数据库&#xff0c;在数据库建立用户表。&#xff08;5分&#xff09; 2…

城市管理违规行为智能识别 Task3学习心得

本次学习主要针对数据集增强和模型预测 1、数据增强&#xff1a; 1&#xff09;将四张训练图像组合成一张&#xff0c;增加物体尺度和位置的多样性。 2&#xff09;复制一个图像的随机区域并粘贴到另一个图像上&#xff0c;生成新的训练样本 3&#xff09;图像的随机旋转、…