UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - 厚积薄发
type: channel
link: https://blog.uwa4d.com/category/technology
description: UWA旨在诊断与优化游戏和VR项目开发过程中潜在的运行性能问题，帮助项研发团队快速检测项目运行时的性能瓶颈，高效定位问题根源，及时反馈针对瓶颈问题的深度分析和解决方法。
items: 如何使Bloom只局部地作用于特效以提高性能
items: 2025年度大赏 | UWA问答精选
items: UE5多线程｜TaskGraph
items: UE是怎么管理纹理的各向异性采样的
items: 参数GPU Write Total Bandwidth的含义是什么，导致其值过高的因素有哪些
items: Unity IL2CPP的GC原理
items: 哪些因素和参数会影响Bloom的性能开销
items: 如何减少托管堆内存碎片
items: UE5多线程｜ThreadPool
items: 如何解决ProtoBuf反序列化中GC高的问题

如何使Bloom只局部地作用于特效以提高性能
type: item
date: 2026-01-13T03:57:00+02:00
description: 1）如何使Bloom只局部地作用于特效以提高性能 2）为什么物理内存一直上涨 这是第460篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From UWA AI 问答 Q：我的游戏中只有主角的各种技能特效需要Bloom，目前是使用的Unity传统渲染管线的官方后处理中的，在低端设备上性能有点差，发烫，使用UWA排查后，主要瓶颈在后处理导致的GPU带宽高。 请问有没有什么办法，可以只对角色的技能特效局部Bloom来提高性能吗？或者还有其他方法，可以不用后处理的Bloom实现类似的辉光效果呢？ A：是的，完全可以通过局部 Bloom（Selective Bloom）和免后处理辉光方案显著降低GPU带宽与发热，尤其适用于“仅主角技能需辉光”的典型场景。以下是经验证的高效方案： 一、局部Bloom（推荐首选） 避免全屏采样，只对技能特效区域做辉光： 原理：将技能特效渲染到独立RenderTexture（带Alpha通道），用自定义Shader对其高亮区域（如亮度>0.8的像素）进行模糊+叠加。 关键优化： 使用1/4~1/8渲染分辨率的RT进行模糊（非原分辨率下采样），带宽下降达75%~94%； 模糊采用2-pass高斯（水平+垂直），每pass仅5tap，避免多级Mipmap下采样； 最终仅Blit一次到主帧缓冲，无额外Fullscreen Pass。 二、免后处理辉光替代方案（零带宽开销） ⚠️ 注意事项 禁用传统Post-Processing Stack v2的全局Bloom Volume，改用Local Volume+Trigger Collider控制作用范围； 所有方案务必关闭Anisotropic Filtering和Trilinear Filtering（对辉光贴图无意义，徒增带宽）； 若使用URP，优先迁移到URP的Bloom（支持Bloom Intensity per Layer），但仍需配合局部渲染逻辑，不可直接开启全局。 回复基于训练数据生成，建议结合其他可靠来源进一步核实。 分享链接： https://www.uwa4d.com/main/uwa-shared.html?shareId=d34a9869-eb45-42cb-bec6-c246a6d83a03 From 问答社区 Q：请问有遇到这种Physics 2D的物理内存一直上涨的问题吗？除了初始化后没有新增物理相关的，但是物理占用的内存却一直上涨： Memory Profiler中是这个在上升： A1：最近有了解到有个办法能看到Unity Subsystem下面Unknown（Unrooted）部分中的内存类型和引用堆栈，不过需要Unity 6+Memory Profiler 1.1.9+编辑器环境，如果能够在这个条件下复现泄露问题，可能能看到更多细节： 这个的效果图供参考： 除此之外，物理这块内存按经验往往是一些引擎生成的不受控制的网格碰撞体容易导致高占用。可以对比看下是在什么条件下观察到该问题的，是否出现在某次版本更新后？也可以开关一些游戏对象、引擎设置里物理相关的功能看看有没有关联。 A2：最终我们发觉是Unity的版本Bug，目前已经做升级处理，从2022.3.62f2升到2022.3.62f3。 欢迎大家转至社区交流： https://answer.uwa4d.com/question/69649adf244ce21ce9ec096a 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859

2025年度大赏 | UWA问答精选
type: item
date: 2026-01-06T04:13:00+02:00
description: 时光流转，深耕不辍。UWA知识型栏目《厚积薄发 | 技术分享》已悄然陪伴大家走过459个工作周。在这一年的技术征途里，我们依旧扎根真实的游戏开发场景，以问答社区的经验沉淀为基石，又迎来了UWA AI技术的全新助力。 2025年的十大精选内容，既收录了社区开发者们互助探讨的智慧结晶，也融入了UWA AI基于海量知识沉淀的精准解答，每一篇分享都瞄准开发中的痛点卡点，为大家提供切实可行的破局思路。 回望这一年，是无数开发者的踊跃分享与积极互动，让技术的价值持续传递；展望2026年，UWA问答社区与UWA AI问答将继续并肩前行，以更专业的内容、更高效的解决方案，陪伴每一位开发者在技术深耕之路上稳步迈进！ UWA 社区主页：community.uwa4d.com UWA QQ群：793972859 From 问答社区 TOP1：如何让GameObject销毁时无论是否Active过，都调用OnDestroy？ 请教一个问题，MonoBehaviour的OnDestroy方法，如果这个GameObject的实例化时就是隐藏的，销毁时不会调用OnDestroy方法。我们项目有些核心逻辑是基于这个OnDestroy做的（时间太久远，耦合太多逻辑不好改出去了）。有没有什么办法，可以让这个GameObject销毁时无论是否Active过，都调用OnDestroy。 A1：要触发OnDestroy，必须要触发过Awake（Awake可以不明着写出来），通常是被实例化或者第一次激活的时候触发Awake，如果一直处于不激活的状态，那么销毁的时候不会自动触发OnDestroy。所以可以在激活的父节点上主动SetActive一下： A2：1. 一般一个Prefab本身是点亮的，大部分是里面的某些节点才会隐藏。如果是我说的这种情况，应该是在根Prefab摧毁的时候主动调用一次需要控制调用的子组件。 如果你真的是根Prefab就隐藏的那更简单了： （1）做一个工具扫描所有Prefab检查并且修改点亮情况； （2）编辑器模式下加载完一个Prefab之后检查是否点亮，如果没有则打印Error。 A3：我们也遇到了这个，主要导致的问题自定义组件清理不掉Lua注册过来的回调，导致返回登录报错。 我这边解决思路是，在脚本反序列化之后，将自己注册到UI根节点的管理脚本上，由这个管理脚本在Destroy时清理下面的子节点。Base脚本如下图： 有几个要点说明下： 我们只处理了UI（其他地方基本不会出这问题），项目的用法上每个UI根节点有必挂的组件，这个组件一定会执行Awake； 如果组件自身Awake走到了，就不需要额外的销毁管理，需要从管理器中将自己移除； 反序列化接口中有一堆限制，要小心处理，比如常用的接口只能在主线程执行，如果在子线程要跳过。还有Prefab打开的编辑状态也有一些状态取不到，我这边直接忽略了（因为不常用）。 欢迎大家转至社区交流： https://answer.uwa4d.com/question/67d390183d306f3e9d594e7a From UWA AI 问答 TOP2：Unity中可以禁用GC吗？ Unity中可以禁用GC吗？ A：在Unity中，可以禁用垃圾回收（GC），但需谨慎使用，并结合具体场景进行管理。 禁用GC的方法 Unity提供了通过脚本控制垃圾回收器行为的API：GarbageCollector.GCMode。 该API支持以下三种模式： GarbageCollector.Mode.Enabled：默认模式，自动触发GC。 GarbageCollector.Mode.Manual：禁用自动GC，但可手动调用System.GC.Collect()或GarbageCollector.CollectIncremental()。 GarbageCollector.Mode.Disabled：完全禁用GC，包括手动调用也无效。 将GarbageCollector.GCMode设置为Disabled即可彻底关闭垃圾回收机制。 使用场景与最佳实践 禁用GC主要用于性能敏感的关键阶段，如战斗、加载、过场动画等，以避免GC导致的CPU卡顿（GC Spike）。 推荐做法： 预分配内存：在进入关键阶段前，预先分配所需的所有对象（如对象池、缓存等），确保运行时无新内存分配。 临时禁用GC：进入关键阶段时设置GCMode = Disabled，结束后立即恢复为Enabled或Manual。 手动清理：恢复GC后，调用System.GC.Collect()回收阶段性内存，防止堆持续增长。 示例流程： // 进入关卡前 AllocateLevelResources(); // 预分配资源 GarbageCollector.GCMode = Ga…

UE5多线程｜TaskGraph
type: item
date: 2025-12-31T03:50:00+02:00
description: 【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ TaskGraph是线程池的进阶，能让任务之间产生依赖，上层可以方便地指定这种依赖。各任务的依赖关系就形成了“图”。 除了线程池，TaskGraph还可以管理GrameThread、RenderThread等独立线程的调度，是UE中最复杂，功能最全面的多线程调度框架了。 典型场景 UE的多线程GC是TaskGraph的一个典型场景，需要把一个大的Array分割成若干小的Array，然后分到多个线程处理，GameThread需要等这些线程都处理完了，再执行以后的任务。代码如下： 注意最后的ParallelFor，把多线程处理封装成并行For行为，分发到多个线程，然后等待多线程执行结束。 如果用普通线程实现这个功能，需要手动用FEvent实现等待，要写一些特化代码。 一、使用TaskGraph 1. Gamethread Tick 最常见的GameThread World Tick，就是由TaskGraph驱动的，因为GameThread也由TaskGraph管理，我们写的Actor::Tick，Component::Tick都在这里执行。Tick函数本身可以包装到TGraphTask里，然后用WaitUntilTasksComplete函数执行所有Task。 2. Async函数 Async函数可以指定EAsyncExecution::TaskGraph，让任务在TaskGraph线程池中执行。还能指定EAsyncExecution::TaskGraphMainThread，让一些短时间任务在主线程执行。 3. WaitUntilTasksComplete 如果需要发出一些异步任务，然后等待执行结束，可以手动构造FGraphEventArray，然后调用WaitUntilTasksComplete等待执行完毕，这里能体现TaskGraph的调度。 二、TaskGraph线程池 TaskGraph包含了线程池功能，不妨首先看线程池部分是如何实现的，这也比较好切入。类似FQueuedThreadPoolBase结构，TaskGraph的线程池有FTaskGraphInterface、FScheduler、FThread、TGraphTask和TAsyncGraphTask。 1. FTaskGraphInterface FTaskGraphInterface是TaskGraph的管理类，是个单例，本身也是Interface，一些重要功能由子类实现。 接口 Startup：初始化TaskGraph。 Shutdown：关闭TaskGraph。 AttachToThread：把一个独立线程添加到TaskGraph中，比如GameThread和RenderThread。 WaitUntilTasksComplete：让一些线程运行若干任务，并在当前线程等待这些任务都执行完。 TriggerEventWhenTasksComplete：当若干任务执行完，触发一个Fevent。 ProcessThreadUntilIdle：让一个NameThread一直处理自己的TaskQueue，直到执行完所有Task。 子类 FTaskGraphCompatibilityImplementation UE5的新TaskGraph子类，实现了TaskGraph的核心功能，不包含任务依赖功能，任务依赖由task实现。 成员 uint32 PerThreadIDTLSSlot：TaskGraph用FWorkerThread结构体管理线程，每个线程在自己的TLS变量中存储指向FWorkerThread结构的指针。 Int32 NumNamedThreads：Named线程数量。 Int32 NumWorkerThreads：Worker线程数量。 Int32 NumBackgroundWorkers：BackgroudWorker数量。 Int32 NumForegroundWorkers：ForegroundWorker数量。 TArray NamedThreads：管理了所有NamedThread。 FTaskGraphImplementation：旧TaskGraph子类实现，不看了。 2. FScheduler FScheduler用于创建、管理Workder线程，以及把Task分派给Worker线程。 成员 TArray<TUniquePtr> WorkerThreads：工作线程。 TAlignedArray<FSchedulerTls::FLocalQueueType> WorkerLocalQueues：Wo…

UE是怎么管理纹理的各向异性采样的
type: item
date: 2025-12-30T03:48:00+02:00
description: 1）UE是怎么管理纹理的各向异性采样的 2）Unity 2022动态设置光照贴图手机端显示异常 这是第459篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From 问答社区 Q：优化我们UE项目的功耗发热问题时，在移动端真机测试中，通过各种性能工具的参数发现GPU带宽很高，且其中Anisotropic Filtered各向异性过滤的比例一直很高。按之前的经验来说，各向异性对带宽的影响就是很大的，但是在编辑器里没找到设置项，请问UE是怎么管理纹理的各向异性采样的？ A：UE中纹理是否开启各向异性需要检查以下设置： Texture Group 在DeviceProfiles.ini中可以创建TextureGroup并进行设置，例如在Engine\Config\BaseDeviceProfiles.ini中（或Project\Config\DefaultDeviceProfiles.ini）： 其中，MinMagFilter是纹理拉伸时使用的过滤方式，包括point、linear、aniso，此处aniso即开启纹理的各向异性设置。如果想对该Group的所有纹理关闭各向异性设置，可以修改为point或linear。 纹理编辑器设置 打开纹理的资产编辑器，在设置中能够通过Filter设置调整纹理采样方式，可选包括Nearest，Bi-linear，Tri-linear，Default。其中，Default为使用TextureGroup的设置，大部分默认的Group中纹理都开启了各向异性（参考上一条）。此处可以选择另外三种过滤方式，选择后将关闭该纹理的各向异性。该设置适合针对单独纹理关闭各向异性时使用。 各向异性最高采样数设置 各向异性的最高采样数可以通过Cvars：r.MaxAnisotropy进行设置。例如： r.MaxAnisotropy=8，则纹理最高采样数为8。数值为0或1时关闭各向异性。该设置为全局设置，对所有开启了各向异性的纹理生效。在Scalability.ini中各纹理级别已经包含了该设置： 此外在DeviceProfile中，也可以直接在对应平台添加该Cvars的设置： 欢迎大家转至社区交流： https://answer.uwa4d.com/question/69523009244ce21ce9ec0967 From 问答社区 Q：编辑器加载显示都正常，出安卓包，光照贴图出现错乱，使用FrameDebugger抓帧查看数据unity_lightmap有值，使用工具获取LightingSetting中设置的光照贴图也是正常的，Mesh中也添加了对应的光照信息数据。 补充测试点：同一个物件，固定在场景中，光照信息正常，动态加载的物件会出现异常（使用Yooasset进行加载）即使动态重新设置了光照贴图，固定的物件光照信息仍然正常。（提供的图片为手机端的显示和FrameDebugger的抓帧数据。） 为什么Unity 2022动态设置光照贴图手机端会显示异常？ 欢迎大家转至社区交流： https://answer.uwa4d.com/question/694eab9d682c7e5cd61bfb83 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859

参数GPU Write Total Bandwidth的含义是什么，导致其值过高的因素有哪些
type: item
date: 2025-12-23T04:11:00+02:00
description: 1）参数GPU Write Total Bandwidth的含义是什么，导致其值过高的因素有哪些 2）对Unity项目来说，性能和效果可以兼顾的物理插件有哪些推荐 这是第458篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From UWA AI 问答 Q：GPU Write Total Bandwidth这一参数的含义，和其过高时可能的影响因素有哪些？ A：GPU Write Total Bandwidth含义： 该指标表示GPU每帧向内存（显存或系统内存）写入的总数据量，单位通常为MB/s或GB/s。它是GPU带宽压力的关键子项，反映帧渲染过程中所有写操作的带宽消耗总和，包括：帧缓冲（Color/Depth/Stencil）、RenderTexture、计算着色器输出、顶点着色器输出（如Transform Feedback）、以及部分缓存写回等。 Write Total Bandwidth 过高的常见影响因素： ✔ 高分辨率多目标渲染（MRT）或高精度帧缓冲 同时写入多个RenderTarget（如GBuffer中Albedo+Normal+Metallic+Depth），尤其使用高精度格式（RGBA16F、RGB10A2）； 主摄像机启用HDR渲染（R11G11B10或RGBA16F），大幅增加Color Buffer写入带宽； MSAA启用后（如4xMSAA），Color/Depth Buffer写入量成倍增长（需resolve前写入+resolve后写入）。 ✔ 频繁或大尺寸RenderTexture写入 全屏后处理中反复创建/清空/Blit到高分辨率RT（如2048×1536的Bloom临时纹理）； UI渲染中大量使用CanvasRenderTarget或Camera.targetTexture； 动态阴影图（Shadow Map）分辨率过高（如4096×4096）且每帧更新。 ✔ 深度/模板缓冲频繁写入与清除 每帧Clear Depth/Stencil（尤其在多Camera、多Pass场景下重复Clear）； 启用深度预通道（Z-Prepass）但未合批或存在冗余绘制，导致两次深度写入（Prepass+Main Pass）； 不合理的ZTest/ZWrite设置（如ZWrite Off缺失导致无效深度覆盖重试）。 ✔ Compute Shader高频大块写入 使用RWTexture2D/RWBuffer执行大规模粒子更新、物理模拟或图像生成，且线程组尺寸过大或Dispatch次数过多； 未使用AppendBuffer/ConsumeBuffer而采用全量写入方式。 ✔ GPU管线冗余写入行为 多个Camera渲染同一区域且未做裁剪/层级隔离，造成重复Color/Depth写入； URP/HDRP中未关闭不必要的Pass（如ShadowCaster、DepthOnly在非阴影场景仍启用）。 快速定位建议： 查看GOT Online GPU报告中Write Total曲线与Front-end Write/Texture Write/Depth Write等子项占比（若支持）； 对比FPS下降时段是否伴随Write峰值，结合GPU Freq是否因带宽瓶颈触发降频； 检查RenderPipeline Asset中是否启用了非必要高精度RT格式、MSAA或多Pass渲染； 使用UWA资源检测报告筛查高分辨率、未压缩的RenderTexture和ShadowMap资产。 优化优先级：降低RT分辨率 → 合并MRT输出 → 关闭冗余Clear/MSAA → 替换高精度格式（如RGBA8替代RGBA16F）→ 后处理降采样。 UWA GPU分析页面中还展示了很多对性能排查分析有一定参考价值的参数和其子参数，若您对这些参数的具体含义和参考价值仍有疑问，可在社区中进行查阅、或尝试使用AI问答助手解答。如以上问题，除了了解写带宽的影响因素外，您还可以提问在不同GPU架构下多种多样的读带宽子项的含义：Front-end Read、LoadStore Read、Texture Read、Vertex Read、SP Read。 分享链接： https://www.uwa4d.com/main/uwa-shared.html?shareId=8919019e-4d69-43a2-bba1-39138791414a From UWA AI 问答 Q：对Unity项目来说，性能和效果可以兼顾的物理插件有什么推荐？ A：在Unity项目中，兼顾性能与效果的物理插件需满足三大硬性条…

Unity IL2CPP的GC原理
type: item
date: 2025-12-17T04:40:00+02:00
description: 【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ 背景：前段时间在项目内做了关于Mono内存（堆内存）的优化。从结果上将Mono内存从220MB降低到130MB，优化过程中唤起了部分关于GC的消失的回忆，虽然实际的优化工作中也许并用不到，但是更明确底层实现机制总归是一件迭代自我的过程，在这里就来回顾一下。 一、什么是垃圾回收 - GC（Garbage Collector） 在游戏运行的时候，数据主要存储在内存中，当游戏的数据在不需要的时候，存储当前数据的内存就可以被回收以再次使用。内存垃圾是指当前废弃数据所占用的内存，垃圾回收（GC）是指将废弃的内存重新回收再次使用的过程。 1. 什么时候触发垃圾回收 有三个操作会触发垃圾回收： 在堆内存上进行内存分配操作而内存不够的时候都会触发垃圾回收来利用闲置的内存。 GC会自动触发，不同平台运行频率不一样。 GC被代码强制执行。 2. GC操作带来的问题 直白点就两个问题：一个是Stop-the-world导致的“卡”；一个是内存碎片导致的“堆内存太大”。 GC操作会需要大量的时间来运行，如果堆内存上有大量的变量或者引用需要检查，则检查的操作会十分缓慢，这就会使得游戏运行缓慢。 GC可能会在关键时候运行，例如在CPU处于游戏的性能运行关键时刻，此时任何一个额外的操作都可能会带来极大的影响，使得游戏帧率下降。 另外一个GC带来的问题是堆内存的碎片。当一个内存单元从堆内存上分配出来，其大小取决于其存储的变量的大小。当该内存被回收到堆内存上的时候，有可能使得堆内存被分割成碎片化的单元。也就是说堆内存总体可以使用的内存单元较大，但是单独的内存单元较小，在下次内存分配的时候不能找到合适大小的存储单元，这也会触发GC操作或者堆内存扩展操作。 堆内存碎片会造成两个结果：一个是游戏占用的内存会越来越大；一个是GC会更加频繁地被触发。 特别是在堆内存上进行内存分配时内存单元不足够的时候，GC会被频繁触发，这就意味着频繁在堆内存上进行内存分配和回收会触发频繁的GC操作。 二、Unity托管堆 在讲具体的Unity GC机制之前再回顾一下Unity托管堆。 1. 托管堆的工作原理及其扩展原因 “托管堆”是由项目脚本运行时（Mono或IL2CPP）的内存管理器自动管理的一段内存。必须在托管堆上分配托管代码中创建的所有对象。 Unity官方文档图 在上图中，白框表示分配给托管堆的内存量，而其中的彩色框表示存储在托管堆的内存空间中的数据值。当需要更多值时，将从托管堆中分配更多空间。 GC定期运行将扫描堆上的所有对象，将任何不再引用的对象标记为删除。然后会删除未引用的对象，从而释放内存。 至关重要的是，Unity的垃圾收集是非分代的，也是非压缩的。“非分代”意味着GC在执行每遍收集时必须扫描整个堆，因此随着堆的扩展，其性能会下降。“非压缩”意味着不会为内存中的对象重新分配内存地址来消除对象之间的间隙。 内存空隙 上图为内存碎片化示例。释放对象时，将释放其内存。但是，释放的空间不会整合成为整个“可用内存”池的一部分。位于释放的对象两侧的对象可能仍在使用中。因此，释放的空间成为其他内存段之间的“间隙”（该间隙由上图中的红色圆圈指示）。因此，新释放的空间仅可用于存储与释放相同大小或更小的对象的数据。 这导致了内存碎片化这个核心问题：虽然堆中的可用空间总量可能很大，但是可能其中的部分或全部的可分配空间对象之间存在小的“间隙”。这种情况下，即使可用空间总量高于要分配的空间量，托管堆可能也找不到足够大的连续内存块来满足该分配需求。 如果分配了大型对象又没有足够的连续空间提供使用则： 运行垃圾回收器，尝试释放空间来满足分配请求。 如果在GC运行后，仍然没有足够的连续空间来满足请求的内存量，则必须扩展堆。堆的具体扩展量视平台而定。 2. Unity托管堆的问题 Unity在扩展托管堆后不会经常释放分配给托管堆的内存页, 防止再次发生大量分配时需要重新扩展堆。 在大多数平台上，Unity最终会将托管堆的空置部分使用的页面释放回操作系统。发生此行为的间隔时间是不确定的，不要指望靠这种方法释放内存。 频繁分配临时数据给托管堆，这种情况通常对项目的性能极为不利。 如果每帧分配1KB的临时内存，并且以60帧的速率运行，那么它必须每秒分配60KB的临时内存。在一分钟内，这会在内存中增加3.6MB的垃圾。对内存不足的设备而言每分钟3.6MB的垃圾也无法接受。 三、Unity的GC机制 -- Boehm GC 以前看过Unity使用的GC方案但最近才惊觉现在使用的Unity都是IL2CPP的版本了，所谓的Mono GC本来就已经不存在了。于是来看下现在的IL2…

哪些因素和参数会影响Bloom的性能开销
type: item
date: 2025-12-16T03:59:00+02:00
description: 1）哪些因素和参数会影响Bloom的性能开销 2）为何Scripting Threads的#0线程数量异常 这是第457篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From UWA AI 问答 Q：哪些因素和参数会影响bloom的性能开销？ A：Bloom（泛光）的性能开销主要来自GPU带宽、计算量与多级采样次数，其关键影响因素和可调参数如下： 一、核心性能影响因素 采样分辨率与下采样层级 Bloom默认从1/2渲染分辨率开始逐级下采样（如Blur1–Blur5），分辨率越低（如1/16）、层级越多，带宽越低但模糊质量下降；反之，高分辨率采样（如1/2）显著增加纹理读取带宽和显存吞吐压力。 移动端建议从1/4或1/8起始，减少首层采样开销。 BloomQuality级别 r.BloomQuality=5（默认）启用全部5层模糊+高质量卷积，开销最大；=1~3可禁用部分层级或使用简化高斯，大幅降低GPU耗时。 ScreenPercentage控制卷积精度 r.Bloom.ScreenPercentage=50表示FFT卷积在50%分辨率下执行；提高该值（如至100%）会线性增加计算与带宽，需谨慎权衡。 泛光卷积（Convolution）启用 启用Bloom Convolution会触发FFT卷积运算，依赖全分辨率内核纹理+额外缓冲区（r.Bloom.ConvolutionBuffer），显著提升GPU计算与显存带宽压力，仅推荐高端设备或离线渲染使用。 后处理链路叠加效应 Bloom常与Tonemapping、Depth of Field、Motion Blur等串联，导致多次全屏纹理读写。尤其在移动平台，多Pass后处理易引发带宽瓶颈（实测可达总GPU带宽的20%+）。 二、关键可调控制台变量（Unreal Engine） 三、优化建议 优先降级BloomQuality+ScreenPercentage，比关闭Bloom更能平衡画质与性能； 使用Stat GPU和Stat RHI定量定位Bloom耗时（查看PostProcessBloom阶段）； 避免在低端机启用Convolution或Dirt Mask（额外纹理采样+混合）； 结合r.VSync与帧率限制，防止Bloom在高帧率下反复重采样。 注：Bloom对GPU温度与功耗影响显著 —— 实测10GB/s带宽可贡献约1000mW功耗（占整机20%+），是移动端发热主因之一。 分享链接： https://www.uwa4d.com/main/uwa-shared.html?shareId=f3f99f69-7e55-4af8-9a58-2531d33ec39a From UWA AI 问答 Q：请教个问题, 我们有用到线程池, 我在Profiler的时候看到在Scripting Threads里面有几十个#0的线程显示, 这个是正常的吗？ A：是不正常的，需立即排查。 Unity的Profiler中出现大量名称为#0的Scripting Threads，通常表明： 线程未正确命名或未注册：Unity默认对System.Threading.ThreadPool中的线程不自动命名，Profiler无法识别其用途，统一显示为#0（或空名+编号）。 线程泄漏风险高：若线程池长期持有大量闲置线程（尤其在Unity 2019 – 2022版本中），可能因ThreadPool.SetMinThreads调用不当、异步任务未完成/未释放、或Task.Run+长阻塞操作导致线程堆积。 非Unity管理线程被误纳入Profiler视图：#0线程无法通过Profiler.BeginThreadProfiling()注册，说明它们未被主动追踪，也意味着你无法在Timeline中定位其耗时来源。 建议动作： 检查是否滥用Task.Run或ThreadPool.QueueUserWorkItem；优先改用Unity Job System或Unity.Collections.LowLevel.Unsafe异步方案。 确认无new Thread(...).Start()后未调用Profiler.EndThreadProfiling()（否则资源泄漏且线程残留）。 在Editor中启用Call Stacks+Deep Profiling，筛选GC Alloc/CPU高耗时帧，定位创建线程的调用栈。 使用UWA GOT Online的Mono/Threading模块，查看线程数趋势与存活时间，确认是否随游戏时长持续增长。 注：Unity官方…

如何减少托管堆内存碎片
type: item
date: 2025-12-09T04:35:00+02:00
description: 1）如何减少托管堆内存碎片 2）MIX贴图中的sRGB（Color Texture）是否要取消勾选 这是第456篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From UWA AI 问答 Q：如何减少托管堆内存碎片？ A：减少托管堆内存碎片是提升游戏运行稳定性和降低GC压力的关键。内存碎片会导致堆空间利用率下降，即使总空闲内存足够，也可能因无法分配连续大块内存而触发不必要的GC或导致分配失败。 1. 识别与诊断 首先需确认是否存在严重碎片问题。使用UWA GOT Online的Mono模式进行测试，重点关注： Mono堆内存总量：观察其增长趋势。 Empty Heap Space（空闲堆空间）：若该值持续增长且占比高，表明存在大量碎片。 GC Allocated波形与实际耗时匹配性：如波峰明显但数值不高，可能因碎片导致GC效率低下。 2. 核心优化策略 避免高频小对象分配 减少byte[]、MemoryStream、DeflateStream等频繁创建的大数组操作。 使用对象池复用常用对象（如List、StringBuilder），避免重复分配与回收。 对于字符串拼接，优先使用StringBuilder并预设容量，避免多次扩容引发的复制。 优化集合类使用 预估集合大小并初始化时指定容量（如new List(100)），防止动态扩容产生临时数组。 避免在循环中调用LINQ方法（如Where、Select），因其常生成迭代器对象造成额外分配。 资源加载与数据处理 加载二进制数据时，使用预先分配的缓冲区（如UnityWebRequest配合DownloadHandlerScript传入固定byte[]），避免每帧生成新数组。 资源按需加载，避免一次性加载过多Asset导致堆膨胀。可结合Addressables实现细粒度管理。 代码层规避装箱 避免将值类型作为Object传递（如字典键值、事件参数）。 使用泛型替代非泛型集合（如Dictionary<int, T>而非Hashtable）。 扩展支持泛型参数的格式化方法（如AppendFormat），消除StringBuilder.AppendFormat中的装箱。 3. 引擎与配置调优 禁用不必要的反射：Type.GetProperty()、GetValue()易引发AOT问题且不利于剥离，尽量通过接口或委托替代。 合理设置GC策略：在移动平台启用增量式GC（Incremental GC），分散收集压力。 控制场景切换频率：频繁加载/卸载场景会加剧堆碎片，可通过异步加载与资源预加载平滑过渡。 4. 工具辅助分析 利用Unity Profiler的Memory模块截取堆快照，查看大块空闲区域分布。 结合GOT Online报告中的驻留对象分析，定位长期存活的小对象来源，针对性重构。 通过以上措施，可显著降低托管堆碎片率，提升内存利用效率和运行流畅度。 分享链接： https://www.uwa4d.com/main/uwa-shared.html?shareId=49ae18c9-5cb3-4f4d-a4d4-a2b451551c11 From 问答社区 Q：我在排查项目中材质资源的时候发现MIX贴图中sRGB（Color Texture）有的有勾选，有的没勾选。 问了AI都是建议取消勾选，但是美术那边又说取消勾选可能效果就不对了。 这边还是问下诸位同僚，你们项目中的MIX贴图里的sRGB（Color Texture）选项是否都是取消勾选的？ A：和美术这边做了几个测试，确认是得取消勾选的。 之前导入资源时默认勾选了这个选项，导致后续都是在错误的基础上调整效果，所以一开始取消勾选会认为效果不对。 实际上是暴露了错误参数下的效果（比方说光滑度过高导致取消勾选后物件变得油腻）。 感谢题主土司喵@UWA问答社区提供了回答，有经验的朋友欢迎转至社区交流： https://answer.uwa4d.com/question/692eb082682c7e5cd61bfb78 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UW…

UE5多线程｜ThreadPool
type: item
date: 2025-12-03T10:53:00+02:00
description: 【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ 当有持续时间短，又比较杂的异步任务时，可以使用ThreadPool，用固定数量的工作线程执行任务，不每次都创建新线程。UE4和UE5的线程池有很大区别，UE4线程池会真的创建很多线程，而UE5主要线程池底层复用了TaskGraph的线程，线程池只是逻辑上的概念。 一、创建线程池 线程池在FEngineLoop::PreInitPreStartupScreen函数中创建。 GThreadPool 类型为FQueuedLowLevelThreadPool，是UE5中的新实现，线程数量由FPlatformMisc::NumberOfWorkerThreadsToSpawn()确定。 GIOThreadPool 类型为FQueuedThreadPool，线程数量由FPlatformMisc::NumberOfIOWorkerThreadsToSpawn()确定，Client为4，Server为2。 GBackgroundPriorityThreadPool 类型为FQueuedThreadPool，Client为2，Server为1。 GLargeThreadPool 类型为FQueuedLowLevelThreadPool，数量由FPlatformMisc::NumberOfCoresIncludingHyperthreads()确定。 二、使用线程池 虽然线程池实现比Runnable复杂，但使用方式也比较简单。 1. Async函数 最常见用法，Async函数可设置EAsyncExecution::ThreadPool参数，指定任务在ThreadPool里执行。 函数内部会创建TAsyncQueuedWork封装Function和Promise，然后使用AddQueuedWork接口把任务加到GThreadPool中。 AddQueuedWork是线程池最重要的接口。 2. AsyncPool函数 与Async类似，但可以指定线程池和Work优先级。 3. 手动调用AddQueuedWork AddQueuedWork函数只需要接受IQueuedWork作为参数，TAsyncQueuedWork只是一个子类，我们可以创建子类，做自定义操作，这样也能指定使用哪个线程池。 比如引擎中Encode LightMap的操作，就使用了FAsyncEncode类： 三、线程池实现 1.类型定义 类型定义可分为线程池，线程池线程，任务。 线程池 FQueuedThreadPool：线程池基类，定义了线程池的接口。 Allocate：创建线程池，类型为FQueuedThreadPoolBase。 Create：创建若干工作线程。 AddQueuedWork：向线程池添加任务。 RetractQueuedWork：撤回任务。 AddQueuedWork和RetractQueuedWork是线程池提供给外部调用的主要接口，注意会在多线程中被调用。 FQueuedThreadPool有多种实现： FQueuedThreadPoolBase 最常用，线程池的基础实现，GIOThreadPool和GBackgroundPriorityThreadPool都会使用。 成员： FThreadPoolPriorityQueue QueuedWork：待处理任务的队列。 TArray<FQueuedThread*> QueuedThreads：等待接收任务的空闲线程。 TArray<FQueuedThread*> AllThreads：所有工作线程。 FCriticalSection* SynchQueue：保护任务队列的CriticalSection，因为任务队列会被多线程修改。 FQueuedLowLevelThreadPool 底层线程使用TaskGraph的ThreadPool，UE5中GThreadPool的默认实现。 FQueuedThreadPoolWrapper FQueuedThreadPoolDynamicWrapper FQueuedThreadPoolTaskGraphWrapper 线程池线程 FQueuedThread：继承自FRunnable，表示线程池中的工作线程。可以想象，它大部分时间都处于idle状态，当有任务来时才工作。 成员： DoWorkEvent：通知线程有任务要执行的Event。 QueuedWork：当前线程正在执行的Work。 Thread：Runnable对应的线程。 函数： Run：主函数，可认为是一个等待、执行任务的循环。 DoWork：由ThreadPool调用，传入一…

如何解决ProtoBuf反序列化中GC高的问题
type: item
date: 2025-12-02T04:26:00+02:00
description: 1）如何解决ProtoBuf反序列化中的GC高的问题 2）如何在编辑器中集成实时查看Mipmap使用层级工具 这是第455篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From UWA AI 问答 Q：有没有比较好的解决ProtoBuf反序列化GC的方案？ A：在Unity项目中，使用ProtoBuf进行反序列化时产生的GC主要来源于两方面：内存分配的临时对象（如字符串、装箱操作）和反射机制的低效调用。以下是针对这些问题的成熟且高效的解决方案。 1. 使用[Preserve]与强制AOT初始化避免反射GC IL2CPP环境下，由于AOT编译限制，ProtoBuf生成代码中的泛型反射方法可能未被提前编译，导致运行时尝试动态调用而崩溃或触发大量GC。为确保所有必要的反射初始化代码被保留： using Google.Protobuf.Reflection; using UnityEngine.Scripting; [Preserve] private void ForceReflectionInitialization() { // 显式调用每个消息类型的ForceReflectionInitialization FileDescriptor.ForceReflectionInitialization<MyMessage1>(); FileDescriptor.ForceReflectionInitialization<MyMessage2>(); // 添加所有用到的消息类型 } 此方法无需调用，仅需存在即可被链接器保留。配合[Preserve]特性防止代码剥离，可有效解决因AOT缺失导致的异常及性能损耗。 2. 避免字符串与值类型装箱，实现0GC解析辅助 参考TMP文本优化思路，可对ProtoBuf解析过程中的输出处理进行定制，减少中间对象分配。例如，在自定义的日志系统或UI更新中直接复用StringBuilder写入，而非通过ToString()创建新字符串。 全局复用StringBuilder用于格式化输出。 对数字字段转字符串时，采用无GC的NumberFormatter.NumberToString(ref char[] buffer, int value)方式，将结果写入预分配字符数组。 自定义IFormatter实现，绕过装箱操作，直接处理基本类型。 3. 使用原生字节访问替代高开销API 对于高频解析场景（如网络同步），考虑绕过完整反序列化流程，直接读取字节流中的关键字段： // 使用CodedInputStream直接读取特定字段 var input = CodedInputStream.CreateInstance(data); while (!input.IsAtEnd) { var tag = input.ReadTag(); switch (tag.FieldNumber) { case 1: int hp = input.ReadInt32(); break; // 只解析必要字段 } } 这种方式跳过对象构建开销，适用于只关心部分数据的轻量级更新。 4. 缓冲池管理Message实例 频繁创建/销毁ProtoBuf对象会加剧GC压力。建议使用对象池缓存常用Message实例： public class MessagePool<T> where T : IMessage, new() { private static readonly Stack<T> _pool = new(); public static T Get() => _pool.Count > 0 ? _pool.Pop() : new T(); public static void Release(T msg) { _pool.Push(msg); } } 结合Clear()重置状态后复用，显著降低内存分配频率。 综上，核心策略是：强制AOT初始化 + 禁止代码剥离 + 复用缓冲区 + 裁剪不必要的反序列化流程。这些方法已在多个上线项目中验证，能有效将ProtoBuf相关GC降至接近零。 分享链接： https://www.uwa4d.com/main/uwa-shared.html?shareId=228136d0-985e-4af9-8c83-f84b04834677 From 问答社区 Q：最近在优化贴图内存，请问有没有开源库或者计算方法，可以检测贴图在屏幕上的占比情况？ 比如，一张1024的贴图，如果渲染时一直使用很高层级的Mipmap，就说明其实不需要102…