关于优化:2020年度大赏-UWA问答精选

54次阅读

共计 14910 个字符,预计需要花费 38 分钟才能阅读完成。

UWA 每周推送的知识型栏目《厚积薄发 | 技术分享》曾经随同大家走过了 252 个工作周。精选了 2020 年十大精彩问答分享给大家,期待 2021 年 UWA 问答持续有您的陪伴。

UWA 问答社区:answer.uwa4d.com
UWA QQ 群 2:793972859(原群已满员)


Q1:IL2CPP 的内存问题

最近看问答下面有个对于 IL2CPP 和 Mono 的比照,看到 IL2CPP 内存冲高会降落。对于这个,我问了 Unity 的官网技术,答复是:你好,Unity 有本人的 GC 机制,为了防止频繁向操作系统申请 / 开释内存,Reserved Mono 值会放弃在肯定区间内,达到某些条件或在某些非凡状况才会触发 GC。有人说是:“内存池治理逻辑都是一样的,属于下层治理一样。它们只是两头语言不一样而已,也是只涨不降。”也有其余大佬说是 IL2CPP 冲高会降落。当初很困惑,求解答。

A:看下来题主说的内存冲高不降,波及两个指标,一个是 Profiler 里的 Reserved Mono,一个是设施内存(PSS)。目前的确没有权威的文档阐明这一点,所以上面通过实在数据来阐明一下。

先说第一个(Reserved Mono)。

  1. 在 Script Backend 是 Mono 的状况下,如果抉择的是旧版本里的 Mono 2.x,或者新版本里的 .Net 3.5(Runtime Version),那么这个值是只升不降的。比方这个数据,Unused 曾经很高了,但也不会降落:

  2. 同样在 Script Backend 是 Mono 的状况下,如果抉择的是.Net 4.x(Runtime Version),那么这个值是能够降落的(但不确定具体是从哪个版本开始的)。比方这个数据,能够看出尽管会降落,也并不是频繁执行降落操作的:

  3. 最初 Script Backend 是 IL2CPP 的状况下,那么这个值也是能够降落的。比方这个数据,看上去和下面的状况相差不是太大:

而对于第二个,设施内存。这个就和安卓零碎的内存管理机制无关了,即便 Unity 把 Reserved Mono 升高了,缩小了本身的内存占用,零碎也不肯定会立刻会把这块内存开释,所以这里的行为就很难说分明了。

该答复由 UWA 提供


Q2:加载配置内存过大问题

配置表太多占用内存过大时,除了采纳 Sqlite,还有什么好的解决办法没有,有没有大佬是否指导下。FlatBuffer 不必全副进内存吗?如果不全副进内存,访问速度如何呢?

A1:第一问题参考如下:

  1. 能够针对反复数据进行剔除,尤其是一些字符串的配置。在配置导出时把这样的数据提取一份,其余用到的中央只是援用,会节俭不少。
  2. 数据类型要正当。
  3. 能够应用相似 FlatBuffer/ZeroFormatter 的提早加载的思路,在真正应用时再去反序列化。一次游戏过程中理论用到的配置量比拟无限,应用这种策略能够尽可能的缩小不必要数据的加载。

第二个问题参考如下:
咱们上个我的项目也是到前期优化时遇到相似问题,只是参考了这种思路,并没有进行齐全替换。咱们过后在打包时,会对配置以行为单位,进行 Offset 和 Length 的计算,在 Runtime 阶段,初始加载只会加载每行的 ID,对应的这一行的 Offset 和 Length,而后后续逻辑调用配置表接口拿数据的时候,如果发现没有反序列化过,就依据 Offset 和 Length 再去构建一下相应的数据提供给下层。访问速度的话必定不如开始间接全副加载好,但咱们测下来影响不大。

感激范君 @UWA 问答社区提供了答复

A2:字符串吃内存不说了,尽量少用或者复用。表格中比拟多的会是那种:攻打 -1000;进攻 -2000;血量 -3000,每个 int 都是 4 个字节,数量多了会顶不住。这种能够思考用一个 int32/int64/uint32/uint64 去存多个数值。

感激萧小俊 @UWA 问答社区提供了答复


Q3:Instruments 如何看 Mono 内存调配

例如在调配了一个 10MB 数组,对应在 Unity Profiler 中会看到开拓了至多 10MB 大小的 Mono 内存。

那么在 Instruments 中,如何查看调配的内存信息呢?Allocations 中的信息是此过程中调配的所有内存信息吗,尝试调配过 100MB 内存,Allocations 中的统计没有任何增长。

A:我这边也做了测试:

创立了 100MB 大小的 int 数组,Size 理论应该是 400MB。

而后到 Profiler 察看:

能够看到 ManagedHeap 正确调配了这 400MB 的空间。

而后打包 iOS 后到 xCode 运行,运行前首先吧 Run 这个 Scheme 的 Malloc Stack 勾上:

Run 当前点选 Memory 并导出 Memory Graph 来察看:

因为应用程序的内存都是在 VirtualMemory 空间调配的,因而查看 VM Regions 的 VM_ALLOCATE 局部。

于是就可已发现 128X3+16 刚好 400MB 的调配。
调用堆栈也很好确定:

正式咱们的测试代码。

而后咱们来看 Instruments。
首先是 Allocations 局部,有一点要留神,该栏的下部有一些选项:

留神最初一个选项,如果抉择第一个:
All Heap & Anonymous VM,All Heap 对应 App 理论调配的物理空间,不蕴含 VM,

Anonymous VM 的官网解释是:
interesting VM regions such as graphics- and Core Data-related. Hides mapped files, dylibs, and some large reserved VM regions。

因而一些比拟大的预留调配空间是不会显示的。
将这个选项切换为 All VM Regions,就能看到调配的 400MB 了:

并且左边详情页面也正确显示了调用堆栈:

另外咱们还能够从 VM Tracker 来察看,关上 VMTracker 的 Snapshots:

于是就能看到这 400MB 的具体调配信息:

能够发现,Virutal Size 略大于 400MB,因为程序其余局部也要申请一些内存。而这 400MB 又别离保留在 Resident 和 Swapped 内,其中 Resident 局部又根本等于 Dirty Size,阐明这部分大小的空间被标记了 Dirty 是不能被替换进来的,剩下 240MB 左右空间是 Clean 空间,能够临时被替换进来以保障有足够的物理空间能应用。这也是因为咱们只是申请了这部分空间,并没有进行具体的赋值初始化和应用。

那如果赋值应用了呢?批改代码测试:

运行 Instruments 后再察看:

能够分明的发现这 400MB 都在 Dirty Size 内。这种状况真正会给该 App 和 iOS 以内存压力。

举荐浏览:
《写给 Unity 开发者的 iOS 内存调试指南》
《Understanding iOS Memory (WiP)》

感激黄程 @UWA 问答社区提供了答复


Q4:URP 对于多个摄相机的性能优化

URP7.4.3,除开主相机外,还有一个子相机,用于将照到的模型渲到游戏主界面 UI 上,在 Profiler 中看到以下状况:

能够看到,在子相机中也进行了包含对 LOD 的计算,但子相机的 Cullingmask 只开了一个名为 RTModel 的 Layer,在这一层里只有一个 3D 对象。按说子相机 CullScriptable 这块开销不应该有才对。

目前狐疑可能的起因是 URP 会对每个 Base Camera 都进行这部分的计算,但如果用 Overlay 相机,又无奈用原来的形式将相机的 targetTexture 渲到一张 RawImage 上了,有人遇到过么?

A:题主的纳闷是:子相机的 CullScriptable 这块的开销不应该有那么大对吧(毕竟只有一个物件)?

这里有两个问题:

  1. Culling 到底做了什么,只有一个物件为什么要 Culling 那么久(难道只有一个物件也要做很多的筹备工作)?
  2. 在 Profiler 外面看到的数据真的是实在数据吗?也就是说,子相机的 Culling 真的做了 1.68ms 吗?

抛开这两个问题,也能够有更好的做法:
咱们一共两个相机,主相机和 UI 相机,那么 UI 上显示的 3D 物件怎么办呢?
咱们有个虚构相机,所谓相机,其实就是做一个 VP 矩阵,做一个 RT,绘制可见的物件就能够了。应用 Unity 的 SRP,随机选一个中央,设置 VP 矩阵,设置 RT,接着绘制指定的物件(UI 中所有的 3D 物件都会挂在这个物件上面),而后这个 RT 就能够随便应用了。

如果一个 UI 上有两个 3D 物件,尽量都放在一个 RT 上;如果不行,就放在两个或更多的 RT 上,只是会多几个绘制命令。几个 RT(还不须要是全屏的),而且会多几个 Swap RT 的操作。因为咱们我的项目没需要须要若干 RT,所以假如一下,在这种须要若干 RT 的状况下,也能够用一个 RT 加多个 Viewport 来解决的。这个代码都是现成的,参考一下 Cascade Shadow Map 的做法,这样 Swap RT 也就省了。

综上所述,既然你都晓得本人要绘制什么,就不要给 Unity Culling 的机会了。

在 Development Build 中连真机看到的性能数据,是实在数据吗?目前在应用相似于 HLOD 的形式来缩小掉这个 LOD 的微小开销。楼上说的“设置一下 VP 矩阵,设置 RT”,还不太分明这个 VP 矩阵的操作具体是个什么,可否详解下或者举荐些相干材料?

A:你提的 HLOD 和 LOD 和下面的 Culling 没关系。VP 矩阵就是 view 矩阵和 projection 矩阵。相机的作用就是提供这俩矩阵的。

如果你在管线外面设置了相应的矩阵,而后绘制指定的物件,就能够齐全不必多一个相机,毕竟多一个相机就多一个 Culling。

如果你对 VP 矩阵不相熟,不分明怎么实现,也简略。仍然用一个额定相机,关上这个相机的 Culling,而后在渲染 pass 中,不要绘制 cullingresult.visibleobject,而间接用 Graphics.DrawMesh 或者 CommandBuffer.DrawMesh 绘制你要显示的那个 3D Object 的物件就好了。

感激王烁 @UWA 问答社区提供了答复


Q5:对于_CameraDepthTexture 的纳闷

如果开启_CameraDepthTexture,Camera 就须要渲染一遍场景内所有带有 ShadowCaster 的可见物体的 Pass 来实现深度图。

然而场景中的物体在开启 ZWrite 的时候就把深度写进了 Depth Buffer 中了,间接取得这个 Depth Buffer 是不是比近乎 DrawCall 翻倍的形式更有效率呢?还是 Unity 在这方面有什么思考?

另外,问一个更理论的问题:
咱们的我的项目须要渲染场景的中湖水的深度成果,所有不通明的场景物体的材质都是关联同样一个 Shader,这个 Shader 是带有 ShadowCaster 的。然而只有个别插入水中的物体须要去渲染 ShadowCaster 的 Pass,有没有办法在不减少 Shader 的状况下,让没有插入水里的物体不渲染 Shadow Caster Pass 呢?咱们用的是 Built-in 的渲染管线。

A1:第一个问题,能够参考这个问题中 Unity 官网人员的回复。
外面讲了两个起因,第一是对于非全屏渲染的状况,原本是想拿对应相机渲染的深度,然而 Depth Buffer 是全屏的。第二个起因是因为很多平台不反对间接拿 Depth Buffer 的数据。

参考网页:
https://forum.unity.com/threads/poor-performance-of-updatedepthtexture-why-is-it-even-needed.197455/

另外查 FrameBufferFetch 相干问题的时候看到 Unity 论坛上另外一个贴子外面的答复。外面说到 Unity 反对了 FrameBufferFetch,然而不反对 DepthBuffer 的获取。

参考网页:
https://forum.unity.com/threads/pixel-local-storage-and-frame-buffer-fetch-on-mobile-devices.604186/

第二个问题,如果不减少 Shader,目前没想到其余好的办法。
如果能够减少 Shader,能够将原来的 Shader 复制一份,只在 ShadowCaster 的局部加一个“NeedDepth”这样的 Tag,将水下的物体的材质球换成这个 Shader,另外做一个只有 ShadowCaster 并带有“NeedDepth”这个 Tag 的 Shader,这个 Shader 用来做 Replace 操作。

额定减少一个 Camera,这个 Camera 追随主相机,或者作为主相机的子节点,创立一个 RT,让这个 Camera 渲染到这个 RT,在 Update 外面应用 ReplaceShader 去画一下,那么只有有那个 Tag 的 ShadowCaster 会进行深度渲染,后续能够对这个 RT 进行编码等操作,这个 RT 记录的就是水下物体的深度。整个过程看上去没有特地多的额定工作,感觉能够一试(我没有做过测试,但实践上是可行的)。

感激 Xuan@UWA 问答社区提供了答复

A2:最近本人试着降级我的项目到 URP,发现 Game View 的湖水深度成果没有了,Scene View 的是失常的,做了很多试验发现了两个景象。

我之前的湖水 Shader 的 Queue 是 Geometry + 150,保障本人在其余不通明物体之后渲染。别的物体有 Shadow Caster 的 Pass,湖水没有,这样在别的不通明物体渲染实现后本人能间接用到正确的_CameraDepthTexture。然而在 URP 下我必须将 Render Queue 设置到 Transparent 层才有正确成果。

发现勾选了 MSAA 抗锯齿后,就有跟 Scene View 一样正确的深度成果了。

URP 默认优先应用 Copy 的形式在所有不通明 Queue 的物体渲染完后把深度 Copy 到_CameraDepthTexture 上,我的湖水 Queue 设置在不通明层了,即便是最初渲染的,他的深度也进入了深度图中,因而成果没了。勾选 MSAA 会失常是因为 MSAA 会影响管线无奈用 Copy 的形式把深度图拷进去(须要 Resolve 解析),所以 URP 默认在这种状况下应用老形式通过渲染 Depth Only (原 Shadow Caster) 的 Pass 失去深度图,因而回到了老形式,我的 Shader 就又起成果了。

其实本人如果过后看到 Framedebugger 的时候,是用心读的而不是只是草草看一眼就花心思在自认为的问题起因上会更容易失去答案。

感激题主安日天 @UWA 问答社区提供了答复


Q6:渲染大面积草地时,如何降低消耗

请问下大家,渲染大面积草地时,如何降低消耗呢?

A1:答复如下:

  1. 应用 DrawMeshInstance;
  2. 下面这个 API 是不会进行视距剔除、视锥体剔除和遮挡剔除的。

上面有两种计划:
a. 将草地按区域分组,用每组的中心点计算视距,根据间隔切换网格 LOD 或剔除;还能用向量点乘简略剔除在相机前方的草地(留神临界问题)。
b. 借助 CullingGroup。
CullingGroup.onStateChanged 事件绑定,通过事件触发调整传入;DrawMeshInstanced 的 Matrix 程序和渲染数量(然而 DrawMeshInstanced 只能指定渲染前几个 Matrix);
通过 cullingGroup.SetBoundingSpheres 实现视锥体剔除和遮挡剔除;
通过 cullingGroup.SetBoundingDistances 实现视距剔除和 LOD。
这个计划最好也进行区域分组,不然 CullingGroup 的事件监听占用会比拟高,在中端机上 4000 个监听会占约 2ms 的大小。

当前如果有比照两种计划的性能,我再进行补充。

附:

  1. 《CullingGroup API 的应用阐明》
  2. 《Unity 3D 研究院之 Lightmap 反对 GPU Instancing》
  3. 《如何高效应用 GPU Instancing 技术来进行草丛渲染》
  4. 降级 Unity 2018,DrawMeshInstanced 不失效的问题

感激题主李先生 @UWA 问答社区提供了答复

A2:应用 Indirect 模式的 Instancing,配合 Compute Shader 实现视锥剔除和遮挡剔除。

感激邹春毅 @UWA 问答社区提供了答复

A3:举荐一个应用 URP 制作的草海成果,亲测可在 Mobile 端应用。
Unity URP Mobile Draw Mesh Instanced Indirect Example 性能测试:

  • can handle 10 million instances on Samsung Galaxy A70 (GPU =
    adreno612, not a strong GPU), 50~60fps, performance mainly affected
    by visible grass count on screen(draw distance = 125)
  • can handle 10 million instances on Lenovo S5 (GPU = adreno506, a weak GPU), 30fps, performance mainly affected by visible grass count on screen(draw distance = 75)

感激 Vest@UWA 问答社区提供了答复


Q7:Packages 目录下的 Shader 打包 AssetBundle

Unity 引入了 Package Manager 来进行治理插件治理,例如 URP 引入 Packages 之后会有目录 Packages/com.unity.render-pipelines.universal@7.3.1。求教一下各位,如何对 Packages 目录下的资源进行 AssetBundle 打包?

例如,工程目录中有材质球援用到 URP 的 Shader,那么材质球打成 AssetBundle 之后会将 Shader 蕴含进去,会有 Shader 解析耗时。

A1:我这边是只应用 SBP 而不必 Addressable,这样通过应用 AssetBundleBuild 是能够将 Packages 中的资源也打包成 AssetBundle 的。

将所有依赖到的 Shader(包含 Packages 中的)都应用 AssetBundleBuild 设置到同一个 shader.bundle 的,打包后也解包确认了,Packages 中的 Shader 也打包在 shader.bundle 而不会被蕴含在材质 AssetBundle 中。

感激黄晓文 @UWA 问答社区提供了答复

A2:我在尝试将现有我的项目转成 URP 的时候,遇到和 Addressable 零碎有些不兼容问题。
在打包援用了 URP 的 Shader 的 Material 时会产生 Shader 被反复打包景象。
如果想把 URP 的 Shader 独自打包,又会发现因为不在 Assets 目录内,Addressable 管不到的问题。

我的解决方案是将用到的 URP 的 Shader 拷进去,放到 Assets 目录下通用 Shader 目录。
当然须要将该 Shader 改名,并且要留神将外部援用的 Shader 也一并拷出治理。

不过个别我的项目中应用的 Shader 往往还是会本人编写,间接应用官网提供总会遇到这种那种问题。因而我也会思考尽量不必官网默认 Shader,这时对于 URP 而言天然更加须要将 Shader 拷进去进行革新了。

感激黄程 @UWA 问答社区提供了答复

A3:通过 黄晓文 的思路,曾经解决。
打包 AssetBundle 最重要的,就是指定资源 Path 的源门路,以及去往的目标 AssetBundle 地址,这个问题要害是须要晓得资源在 Packages 中的源门路。

例如一个 Packages 下的 Shader 资源,Lit.shader,通过 AssetDatabase.GetAssetPath 能够发现门路是:Packages/com.unity.render-pipelines.universal/Shaders/Lit.shader,这个是正确的门路,用它即可。

而谬误的门路别离是:

  1. Unity 中看到的:Packages/Universal RP/Shaders/Lit.shader 谬误。
  2. 在文件目录中看到的:Packages/com.unity.render-pipelines.universal@7.3.1/Shaders/Lit.shader 谬误

所以得出结论:Packages 下的资源打包,去除一下 @x.y.z 即可。

感激题主一刀 @UWA 问答社区提供了答复

A4:能够试试应用 Scriptableobject 或 Material 援用到 Shader 文件,而后把 ScriptableObject 或 Material 打到 AssetBundle 里。

感激上午八点 @UWA 问答社区提供了答复


Q8:Shared UI Mesh 内存占用过高

缓存池中的 UI 如果不暗藏,Shared UI Mesh 会比拟高;如果暗藏,Shared UI Mesh 会比拟低,然而 UI SetActive 又有性能耗费,该如何衡量呢?

暗藏缓存池中的 UI 时,Shared UI Mesh 内存占用:

不暗藏缓存池 UI 时 Shared UI Mesh 内存占用:

A1:Shared UI Mesh 是源自 UGUI 框架中的一个动态全局变量 Graphic.workerMesh:

而 workerMesh 次要在以下代码中应用:

该函数是在 Rebuild 单个 UI 元素的顶点信息,红框里的 FillMesh 就是将更新后的顶点属性数组设置到 workerMesh 上,且每次调用都会先进行 Clear 操作。

看逻辑,这个 workerMesh 的内存大小应该只和单个 UI 元素的顶点量无关,但理论测试下来,是和以后所有激活 UI 元素的顶点总量相干的。

所以,Shared UI Mesh 很大,示意以后所有激活 UI 元素的顶点总量很高。须要对局部简单元素进行简化。

常见的简单元素有:

  1. Tiled 模式的 Image:该模式下会依据 UI 元素的区域和纹理分辨率的大小,主动生成适当数量的四边形,一旦纹理分辨率很小,而区域很大时,就会产生大量的顶点。
  2. Outline 成果的 Text:Outline 成果会将 Text 文本原来的顶点数放大为 5 倍。
  3. RichText,且蕴含了较多样式的 Text:款式标签局部也会产生顶点数。
    注:2 和 3 同时应用时,款式标签局部的顶点数也会放大。

定位的办法:

  1. 初步定位:间接在 SceneView 下换到线框模式,肉眼找一下简单元素;
  2. 通过 Profiler 的 UI 面板,查看各个 Canvas 下各个 Batch 产生的顶点数,并查看对应的 GameObject 即可。

须要留神的是,Canvas 组件被禁用的状况下,Profiler 里是看不到的,但其下的激活 UI 元素仍然会影响 Shared UI Mesh 的大小。

该答复由 UWA 提供

A2:如果只有 SetActive 能力升高 Shared UI Mesh,如同就没有其余抉择了;然而如果切换 layer 能够升高,能够抉择该方法。

感激青麈 @UWA 问答社区提供了答复

A3:也能够试试把 Canvas 的 Enable 设置为 False。

感激 Crazy_Liu@UWA 问答社区提供了答复


Q9:Addressable 如何删除旧资源

目前打算应用 Addressable 来实现资源热更新,理论真机测试发现当资源更新后,旧的资源 Addressable 并不会把它删除,同时能够看到 App 占用的数据文件会越来越大。请问有什么方法能够把指定的 Group 或 Label 的资源删除吗?

试了 Addressable.ClearDependencyCacheAsync 也不行。理论测试这个接口只能删除最新版本的资源。当本地曾经是最新版本资源时这个接口的确无效;然而如果本地须要更新资源时,这个接口应该也是尝试去删除最新资源,然而本地并没有最新版的资源,所以大略就有效了。

A:调用 Addressable.ClearDependencyCacheAsync 本质是调用了“Caching.ClearAllCachedVersions();”。事实上是应用了 Unity 的 Caching 零碎。

在 Windows 编辑器环境测试了一下。
Caching 的目录为“C:UsersUserNameAppDataLocalLowUnityProjectFolder”,当失常下载 AssetBundle 当前,该目录内就呈现“stage01_298bd883434eedb69ea7316cb23e0b0d662ab7a0d2aa99bc7a2dbb7baec63872”之类的目录,并保留着以后的 AssetBundle 版本,当更新 AssetBundle 并执行下载当前,该目录也会呈现其余 AssetBundle 的 Caching 目录。

在执行下载之前,先执行了一下“Caching.ClearCache();”,这时会发现 Caching 目录内曾经被清空,所有版本的 AssetBundle 都没有了。下载实现后,该目录只保留了最新的 AssetBundle 资源。由此可推,即便不通过 Addressable 零碎,依然能够通过 Caching 把所有的资源都清理掉。

于是持续进行第二个试验,间断更新几次 AssetBundle 当前,Caching 目录内曾经有多个版本的 AssetBundle 目录了,当有新的更新后执行“Addressables.ClearDependencyCacheAsync(key);”,发现确实并没有将旧版本的 AssetBundle 都删除。因为“Caching.ClearAllCachedVersions”的参数是对应的 AssetBundle 名字,而 Addressables 的治理 AssetBundle 包名是带 Hash 的,因为每个版本的 AssetBundle 文件名都不一样的,Caching 零碎也就无奈分辨了。

持续做试验,将打包名字去掉 Hash,Caching 目录内的 AssetBundle 目录名也不带 Hash 了,而后间断更新几个版本后发现,该 AssetBundle 目录内多了几个不同 Hash 版本的目录,外部才是真正的 AssetBundle。于是走“Addressables.ClearDependencyCacheAsync(key)”,这时就能正确地删除旧版本,而后再更新新版本了。

的确不勾选 Hash 打包能够胜利删除了,这种形式貌似就是笼罩式的打包,不晓得会不会有其余隐患,目前来看够用。

A:隐患就是如果依照 Label 来做更新查看,原本能够只下载差别局部,然而因为同样应用 Label 做革除 Caching 的工作就会造成反复下载本来不必要更新的局部。于是就须要遍历所有的 Location 而后去查看更新,并将有更新的 AssetBundle 放入列表,而后再顺次革除旧缓存,从新下载。这样就和传统计划没太大区别了。

请问下不勾选 Hash 其实就不必革除了吧?名字一样不是会间接笼罩吗?

A:不勾选 Hash,只是在 Cache 的目录内第一级资源同名子目录是统一的,然而外面保留具体数据的子目录是递增的,因为有不同版本。每个版本都会有一个子目录。这个是 Caching 系统管理的。

如果不勾选 Hash,CDN 有可能不会更新文件,所以要联合本人的我的项目应用的 CDN 状况来确定如何治理这块。

我是用 Addressables.ClearDependencyCacheAsync(key) 并没有革除 Cache 屡次更新后越来越多。下面所说的“将打包名字去掉 Hash”,是指配置中 Bundle Naming 抉择 Fliename 选项嘛?所应用的 Key 是指本次更新的列表吗?

A:就是“配置中 Bundle Naming 抉择 Fliename”,这个 Key 其实是应该是这个 Group 名字,对应到打包后的 Bundle 名字,让 Caching 零碎搜寻。貌似 1.15.X 前面这块有一些更新,还没确认过。

感激黄程 @UWA 问答社区提供了答复

A:在论坛里看到一个计划(11 楼),批改了 Addressables.ClearDependencyCacheAsync(key) 的实现:

首先在 Addressables.ClearDependencyCacheForKey 中对以后应用的资源进行 Cache 标记,而后在 Addressables.ClearDependencyCacheAsync 里革除游戏运行之后未应用的资源。

批改 AddressablesImpl.cs 文件中的以下四个办法:

  • ClearDependencyCacheForKey(object key)
  • ClearDependencyCacheAsync(object key)
  • ClearDependencyCacheAsync(IList locations)
  • ClearDependencyCacheAsync(IList keys)

须要留神调用 Addressables.ClearDependencyCacheAsync 的机会。

 internal void ClearDependencyCacheForKey(object key)
        {
#if ENABLE_CACHING
            IList<IResourceLocation> locations;
            if (key is IResourceLocation && (key as IResourceLocation).HasDependencies)
            {foreach (var dep in (key as IResourceLocation).Dependencies)
                    Caching.ClearAllCachedVersions(Path.GetFileName(dep.InternalId));
            }
            else if (GetResourceLocations(key, typeof(object), out locations))
            {foreach (var loc in locations)
                {if (loc.HasDependencies)
                    {foreach (var dep in loc.Dependencies){
              // added by Lukas
                            AssetBundleRequestOptions options;
                            if ((options = dep.Data as AssetBundleRequestOptions) != null)
                            {
                   // 对以后依赖资源进行标记
                                Caching.MarkAsUsed(dep.InternalId, Hash128.Parse(options.Hash));
                            }
                            // 原办法无奈删除旧版本的 ab
              //Caching.ClearAllCachedVersions(Path.GetFileName(dep.InternalId));
                        }   
                    }
                }
            }
#endif
        } 
 public AsyncOperationHandle<bool> ClearDependencyCacheAsync(object key)
        {if (ShouldChainRequest)
                return ResourceManager.CreateChainOperation(ChainOperation, op => ClearDependencyCacheAsync(key));

            ClearDependencyCacheForKey(key);
            // added to ClearCache 
        Caching.ClearCache((int) Time.realtimeSinceStartup + 10);
            var completedOp = ResourceManager.CreateCompletedOperation(true, string.Empty);
            Release(completedOp);
            return completedOp;
        } 

感激小魔女纱代酱 @UWA 问答社区提供了答复

A:咱们用的是笼罩式更新的流程(不是增量更新)。Addressables 版本是 1.15.1。

在把 Bundle Naming 设置为 Filename 后发现,在 Caching 中的 AssetBundle 目录还是带有 Hash 值的,这个和楼上的解释不统一,不晓得是不是版本的起因。

图中可看到 AssetBundle 包名曾经是 Group 的名字了,然而下载到 Caching 中还是有 Hash。

而后咱们是这么解决的。还是开启文件名的 Hash,将 Caching 中的 AssetBundle 文件夹名保留到 PlayerPrefs 中,当检测到有下载的时候,读出 PlayerPrefs 中的值,把旧的对应 AssetBundle 包删除,并更新 PlayerPrefs。

获取以后 Catalog 中所有 AssetBundle 文件夹名的办法,是从 Addressables 中复制进去的。

 // 取得以后 catalog 中所有 assetbundle 保留的文件夹名
    // 这个函数中援用到的办法没有列出,能够去 addressables 中源码中找 
    // 示例:CollectBundleNames(new string[]{"SkllIcons", "ItemIcons", "AvatarIcons"})
    private static List<string> CollectBundleNames(object[] keys)
    {List<string> result = new List<string>();
#if ENABLE_CACHING
        foreach(var key in keys)
        {
            IList<IResourceLocation> locations;
            if (key is IResourceLocation resourceLocation && resourceLocation.HasDependencies)
            {foreach (var dep in resourceLocation.Dependencies)
                {if (dep.Data is AssetBundleRequestOptions options)
                    {result.Add(options.BundleName);
                    }
                }
            }
            else if (GetResourceLocations(key, typeof(object), out locations))
            {var deps = GatherDependenciesFromLocations(locations);
                foreach (var dep in deps)
                {if (dep.Data is AssetBundleRequestOptions options)
                    {result.Add(options.BundleName);
                    }
                }
            }
        }
#endif
        return result;
    } 

删除 AssetBundle 包文件夹的办法:

// 这里的 bundleName 就是 CollectBundleNames 的返回值
private static void ClearCacheForBundle(string bundleName)
    {List<Hash128> hashList = new List<Hash128>();
        Caching.GetCachedVersions(bundleName, hashList);
        foreach (Hash128 hash in hashList)
        {Caching.ClearCachedVersion(bundleName, hash);
        }
    } 

须要留神的是调用 CollectBundleNames 的机会,如果曾经更新了 Catalog,那么返回的是行将要写入 Caching 中的 AssetBundle 文件夹名。如果要失去以后 AssetBundle 文件夹名,要在更新 Catalog 之前调用。

感激 jim@UWA 问答社区提供了答复


Q10:LuaJIT 性能热点函数优化

我的项目中的这个函数耗时十分重大,有什么优化的办法吗?

A1:这个是 table.get,对应获取字段或者拜访数组时调用的函数:

  1. 优先应用间断数组而不是英文名字段,能够显著晋升拜访效率并升高内存耗费,很多团队喜爱应用 Class 的写法,能够这样革新:
    local obj = ClassA.New()
    obj.abc = 1
    obj.cde = “test”
    变成:
    local obj = ClassA.New()
    obj[1] = 1
    obj[2] = “test”
    这个办法能够针对应用频率较高的代码进行革新。
  2. 本人开发工具,在编译 Lua 之前,将 Lua 代码中的常量从英文名变量转换为数值,这个能够联合 1 应用,就能够在开发期写英文名字段名,而后编译时转换为数组。

感激招文勇 @UWA 问答社区提供了答复

A2:字符串应该是哈希值计算的耗费,这样的开销应该是很频繁地调用了。

感激王欢 @UWA 问答社区提供了答复

明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在 UWA 问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。

官网:www.uwa4d.com
官网技术博客:blog.uwa4d.com
官网问答社区:answer.uwa4d.com
UWA 学堂:edu.uwa4d.com
官网技术 QQ 群:793972859(原群已满员)

正文完
 0