惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Security Affairs
WordPress大学
WordPress大学
MongoDB | Blog
MongoDB | Blog
A
About on SuperTechFans
F
Fortinet All Blogs
Hacker News: Ask HN
Hacker News: Ask HN
酷 壳 – CoolShell
酷 壳 – CoolShell
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - Franky
Hacker News - Newest:
Hacker News - Newest: "LLM"
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Tenable Blog
Hugging Face - Blog
Hugging Face - Blog
Recorded Future
Recorded Future
NISL@THU
NISL@THU
SecWiki News
SecWiki News
Cyberwarzone
Cyberwarzone
Stack Overflow Blog
Stack Overflow Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V2EX - 技术
V2EX - 技术
Simon Willison's Weblog
Simon Willison's Weblog
云风的 BLOG
云风的 BLOG
Microsoft Azure Blog
Microsoft Azure Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
D
Docker
C
Check Point Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
S
Schneier on Security
博客园 - 【当耐特】
雷峰网
雷峰网
月光博客
月光博客
H
Help Net Security
人人都是产品经理
人人都是产品经理
博客园 - 三生石上(FineUI控件)
Google Online Security Blog
Google Online Security Blog
L
LINUX DO - 最新话题
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
The GitHub Blog
The GitHub Blog
H
Hacker News: Front Page
D
Darknet – Hacking Tools, Hacker News & Cyber Security
AI
AI
Cisco Talos Blog
Cisco Talos Blog
G
Google Developers Blog
V
Vulnerabilities – Threatpost
TaoSecurity Blog
TaoSecurity Blog
T
Troy Hunt's Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
The Blog of Author Tim Ferriss

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 使用mmap来学习/proc/pid/smaps 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 游戏服务器分布式数据的一种同步的思路 为silly增加了互斥锁 2022(完) Go语言之闭包篇 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 重构登录逻辑 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
Unity资源管理(续)
重归混沌 · 2019-08-31 · via 重归混沌的BLOG

上次增加资源半自动释放机制之后,根据log显示已经有30%~40%的资源被释放掉了(大部分为UI资源)。然而内存问题,并没有明显改善。

我们的资源管理是通过一个叫做ABM的模块进行管理的,ABM采用标准的引用计数方案来管理资源和AssetBundle的生命周期。

当使用ABM.load_asset时会首先检查这个资源的引用计数是否为0,如果为0则会触发加载相应AssetBundle操作。

加载AssetBundle时会首先检查AssetBundle的引用计数是否为0,如果不为0则直接将引用计数加1.并将结果返回即可。

如果AssetBundle的引用计数为0,则除了要对引用计数加1之外,还需要递归加载其所依赖的其它AssetBundle。

如果所需要加载的AssetBundle有依赖于其他AssetBundle,则先递归加载所有依赖的AssetBundle,最后再加载自身。

当卸载一个资源时,首先将其引用计数减1,然后检查其引用计数是否为0. 如果引用计数为0,则触发AssetBundle卸载操作。

卸载AssetBundle时,同样将其引用计数减1,如果引用计数为0,则调用Unity接口AssetBundle.Unload(true)真正释放AssetBundle. 同时递归卸载依赖的所有AssetBundle。

由于担心资源泄漏不可控,所以我们并没有使用AssetBundle.Unload(false)来进行AssetBundle释放。


在整个流程,只有AssetBundle的引用计数为0时,才会被调用Unity接口进行释放。而仅在AssetBundle被释放时,其内部被加载过的资源才会被释放。

换句话说,假如一个AssetBundle有10个资源,如果先加载其中9个,再加载任意2个,最后再将上次加载的9个资源进行释放。这10个资源就会同时存在于内存之中。

因此一个AssetBundle的利用率(即AssetBundle中同时引用计数大于0的资源数量除以AssetBundle中的资源数量)越低,这个AssetBundle中的资源同时出现数量就会越少,相邻两次同时存在内中的资源集合的交集就可能会越小,资源内存泄漏的概率就越大。

当然事情并不是绝对的,如上面的例子,如果先卸载9个资源,并且此AssetBundle没有被其他AssetBundle所依赖,就会产生一次AssetBundle.Unload(true),此时再加载此AssetBundle中的2个资源时,这个AssetBundle会被重新加载,并从AssetBundle加再次加载所需的2个资源。这样就不会有内存泄漏。

但是实际的AssetBundle依赖关系及资源加载顺序都比较复杂,很难保证上述良好的情况及顺序。因此我认为AssetBundle的利率用还是有很大的参考价值的。

为此,我写了一个AssetBundle Profiler,通过折线图来实时显示当前所有AssetBundle的平均利用率。

当选中某个采样点时,可以详细看到此时每个AssetBundle的利用率,并可以查看任意一个AssetBundle中所有资源的引用计数情况,及所在的图集(如果是图片的话)。

相信通过连续观察某一个AssetBundle的资源的详细加载情况,可以为我们如果优化拆分AssetBundle有不错的帮助。比如,我们可以通过资源的使用频次,根据频次的多少来做成树状态倚赖的AssetBundle关系,或者将逻辑上互斥的资源拆分成多个AssetBundle, 即使这些资源只属于同一个模块使用。


然而,上面的方法只能一定程度上改善内存泄漏问题,并不能真正解决问题。因为我们真的很难保证AssetBundle中的所有资源一起加载一起释放。

最好的办法就是,在一个资源的引用计数为0触发卸载AssetBundle之前,先调用Resources.UnloadAsset,将这个资源进行释放。

上述ABM的实现中,没有执行此操作的原因在于,当被加载的资源是一个Prefab时,他可以引用各种其他资源,这些资源会被AssetBundle.LoadAsset(Async)时, 会被Unity底层进行加载。

如果这个Prefab所依赖的资源在其他AssetBundle中,Unity就会通过AssetBundle的依赖关系告诉我们,需要加载这个被依赖AssetBundle。 至于Prefab所用的资源则由Unity底层自动加载,上层的ABM是感知不到的。

换句话说,ABM中的资源的引用计数是不准确的,它的精度仅能够正确指示AssetBundle的生命周期,而不能正确指示其自身的生命周期。

如果要想使资源的引用计数准确指示出资源的生命周期,就必须在加载Prefab这类引用其他资源的资源时,修正所有被Prefab依赖的资源的引用计数。

如果要这么做,就必须要做好两件事。

  1. 如何运行时获取资源之间的倚赖关系

  2. 如何时处理资源与AssetBundle的引用问题

对于第一件事,Unity并没有提供运行时获取资源之间的依赖关系。幸运的是,AssetDatabase.GetDependencies提供了一个非运行时接口。因此我们可以在生成AssetBundle时,通过生成一个文件来存储资源间的依赖关系并在运行时解出。

如果一个资源被“引用”之后引用计数为1,则递归对它所依赖的所有资源引用计数加1。同样如果一个资源被卸载后,如果引用计数为0,则递归对他所依赖的资源引用计数减1。

由于Unity底层会自动加载被依赖的资源。为了避免无谓的开销,在操作引用计数时,我们仅仅去修正资源的引用计数而并不实际去加载资源,同样也不需要去加载相应的AssetBundle(也不去操作其引用计数)。

这对我们做好第二次事,造成了困扰。因为此时资源的引用计数与AssetBundle的引用计数完全割裂了。我们需要重新找到一个判断何时加载AssetBundle的依据。

最开始我认为应该将资源间依赖产生的引用计数单独记录,可以叫depend_ref, 而原先的引用计数ref含义及触发的操作保持不变。

如此,我们就可以在产生资源依赖时修正depend_ref,而在资源释放时,如果ref和depend_ref同时为0,则调用Resources.UnloadAsset。

这样做一定是对的,因为其实depend_ref与AssetBundle之间的依赖关系其实是重合的,所以就算ref的值为0而触发AssetBundle卸载操作,只要depend_ref不为0,这个AssetBundle的引用计数一定大于1,从而不可能被真正释放。

经过观察发现,而在释放时,depend_ref+ref一起产生作用,在加载时,ref单独产生作用,在处理依赖关系时depend_ref单独产生作用。

而ref产生的惟一作用就是当它为0时,进行加载和卸载AssetBundle。

前面说过就算ref等于0只要depend_ref大于0,AssetBundle是不可能被卸载的,因为depend_ref本质是反应的是AssetBundle之间的依赖关系,同时因为depend_ref大于0,说明依赖他的资源还在内存中,因此资源不能释放,他所在的AssetBundle也不能释放。因此depend_ref和ref逻辑是统一的,可以进行合并。

而在加载时,ref的作用仅仅是在等于0时进行AssetBundle加载。这是一个2值变量,就是说要么是true, 要么是false。恰好我们有一个相似的变量可以使用,每个被加载成功且没有卸载资源,ABM必须要保留其引用,以避免重复加载。当我们卸载此资源后,为了避免被误为,一般会将资源引用置为null。终于,ref可以和depend_ref进行合并了,我们的抽象更完美了一些。


修改后ABM流程如下。

当加载一个资源时先通过资源间的依赖关系修正相关资源的引用计数,以正确表明资源的生命周期。

判断需要通过ABM加载的资源的引用是否为null,如果不为null则直接返回。如果为null则加载相应的AssetBundle(处理AssetBundle及其依赖的引用计数逻辑不变)。

当卸载一个资源时同样根据资源间的依赖关系修正相关资源的引用计数,如果引用计数为0,且ABM持有的资源引用不为null,则调用Resources.UnloadAsset同时触发AssetBundle的卸载操作。然后将资源引用置为null。

ps. "释放"指通过AssetBundle.Unload(true),而"卸载"指处理AssetBundle的引用计数,如果为0,则真正"释放"

pps. 如果多张图片在一张图集里,只要图集中任意一张图片没有Resources.UnloadAsset,整张图集也不可能被卸载。