拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Unity移动端发热优化:CPU开销三巨头GC、Draw Call与Canvas重建实战

1. 先把锅分清楚CPU 在 Unity 项目里到底忙什么移动端 Unity 项目的发烫问题一出来团队第一反应通常是“GPU 跑不动了”“特效堆太多”。但我在实际优化里见过太多反例GPU 占用肉眼可见不高真机后盖照样烫手帧率像心电图。真正的问题反而在 CPU——GC 分配、Draw Call 提交、Canvas 重建这三件事每一件都能让 CPU 忙到起火。这篇文章就专门聊这三个 CPU 开销以及怎么用 Profiler 把它们从“感觉卡”一步步挖到“具体哪一行在烧电”。先给结论如果你想在移动设备上做发热优化CPU 预算必须和 GPU 预算一起看单独盯 GPU 基本等于白做。因为发热不是某一颗芯片专属的锅而是整机功耗和散热共同作用的结果。CPU 一旦长时间跑在 18ms、20ms 的主线程帧开销里芯片温度会快速爬升系统再给 CPU 降频帧率就跟着崩了。这时候你调阴影、压分辨率、降粒子可能 GPU 那边轻松了但用来计算逻辑、提交渲染、重建 UI 的 CPU 还在原地打转问题照样在。1.1 发热、掉帧和 CPU 频率的关系手机不像台式机有高功率散热器体积限制决定了它的散热能力很有限。CPU 和 GPU 往往是同一颗 SoC 上的两块芯片共享热设计功耗其中任何一边持续高负载整机温度都会上来。温度一高系统会主动降频保护硬件降频之后帧时间就会变长于是你看到的就是“首发跑 60 帧十分钟后变 30 帧”。这种场景和 GPU 关系未必大。我遇到过不少项目用 Profiler 一抓GPU 帧时间才 6msCPU 主线程却已经 20ms 了。这个时候如果继续优化 GPU效果就非常有限。CPU 的持续高负载才是发热的真凶。特别要留意 GC它平时不显眼但积累到阈值之后的回收瞬间主线程会卡出一个肉眼可见的长帧。长帧本身不掉电掉电的是你为了补帧而让 CPU 继续满负荷跑的那段时间。另一个容易被忽略的点是手机上的 Unity 游戏并不是独占 CPU。系统本身有后台进程、其他 App 的残留服务、网络模块、音频线程都在抢 CPU。CPU 长时间处于高频状态功耗会指数级上升。所以发热优化里很重要的一件事就是尽量压低 CPU 的平均负载而不只是压低峰值帧时间。1.2 Unity 主循环里CPU 的活都有哪些要定位 CPU 的问题得先知道 CPU 在 Unity 的一帧里到底做什么。简单划分一下主循环里大部分工作都是 CPU 在协调逻辑层Update、FixedUpdate、协程、物理查询、动画回调、事件派发。这些代码里最容易埋 GC Alloc也最容易因为写得不谨慎而把每帧时间吃掉。数据层网络包的解析、配置表读取、资源加载、对象池管理。这部分如果放在主线程很容易造成阻塞尤其是 Resources.Load、AssetBundle 加载这种重操作。渲染提交层裁剪、合批、上传变换矩阵、提交渲染命令、处理 GPU 回读。很多人以为这些是 GPU 干的其实提交之前的准备工作全部是 CPU 在干Draw Call 就是这一层的最大开销之一。UI 层UGUI 的布局计算、网格重建、批次重建。这套东西具有非常强的“牵一发动全身”特征一个 Text 改了可能一小片 UI 都要重新算。内存层托管堆分配、GC 回收、引用计数、资源加载和卸载。GC 是移动端 Unity 项目里最神秘的 CPU 消耗来源因为它在时间轴上不是匀速出现的。我遇到很多项目定位发热问题只查脚本 Update 里的算法复杂度查完发现脚本很快但还是烫。原因就是脚本确实不慢但脚本每帧偷偷做了分配GC 隔一段时间跑一次把主线程卡到 100ms 以上你看到的是“偶尔顿一下”后台却是 CPU 在反复承担高负载。所以CPU 优化的关键是找出那些“看起来不慢、但持续在做无用功”的代码路径。GC、Draw Call 和 Canvas 重建正好是这三类问题里最典型的三个。2. GCUnity 里最常见的 CPU 隐形炸弹GC全称 Garbage Collection也就是垃圾回收。在 Unity 的移动端优化话题里它是出场率最高的词之一。很多开发者对 GC 的理解是“偶尔卡一下”但它对功耗的影响远比表面更严重。2.1 分配一多CPU 不一定会立刻卡Unity 的托管堆Mono/IL2CPP在工作时有一个特点它不会每帧都去做完整回收而是先不断分配内存等到托管堆达到一定的阈值之后再触发一次 GC。这个机制意味着你写了几十行每帧分配少量内存的代码前几十帧都看不出问题但只要触发一次 GC主线程就可能被拖进去几十毫秒。更麻烦的是GC 之后如果托管堆还是不够用Unity 会申请一块更大的堆内存。移动端的物理内存有限堆变大不仅让后续 GC 扫描范围更大还容易触发系统层面的内存压力甚至被系统杀死进程。所以我一直跟团队说GC Alloc 不是“可选项”只要每帧超过一定量就必须当成发热和卡顿的元凶来看。打个比方你每天早上往门口放一个小纸箱当天不觉得碍事但纸箱越堆越多等到需要进门时你得花好几分钟把纸箱全部搬走。GC 就是那个搬纸箱的动作。纸箱放得越勤搬一次就越痛苦。2.2 几个容易产生 GC 的日常写法很多 GC 分配并非来自复杂算法而是来自最普通的日常写法。这里列几个我见过频率最高的例子第一个是字符串拼接。比如下面这行看起来人畜无害void UpdateScore(int score) { scoreText.text 分数 score; }字符串是引用类型分数 score在 C# 里会先生成一个新的字符串对象再赋给scoreText.text。如果这段代码每帧都执行那就等于每帧产生一个新字符串。哪怕 UI 上的数字根本没变分配也已经发生了。更麻烦的是scoreText.text赋新值后Canvas 还会因为这个文本变化而重新生成网格这就把 GC 和 Canvas 重建两个问题串到了一起。第二个是装箱。往object类型的参数里传值类型时编译器会把它包装成一个对象这个包装动作就有分配。典型的场景是拿enum去拼字符串或者往Debug.Log里传数字object tag SkillType.Fire; // 枚举装箱 Debug.Log(当前状态 playerState); // 字符串拼接 可能装箱Debug.Log 因为要支持任意类型参数经常会走object如果不小心把数字和字符串反复拼接每帧都会产生分配。即使是正式包关闭了日志输出前面的字符串拼接代码本身依然会执行。第三个是 LINQ。Unity 的 ILC2CPP 对 LINQ 的优化并不像你想的那么彻底尤其是配合匿名函数和闭包的时候几乎必然会分配。比如var count activeList.Count(x x.activeSelf);这句话确实写起来舒服但在移动端每帧调用时代价可能比你自己写一个 for 循环高一个数量级。我不是说任何时候都不能用 LINQ但在每帧执行的热路径里能用普通循环就用普通循环。2.3 怎么证明 GC 在拖累 CPU怀疑 GC 只能算第一步真正要定位还是得上 Profiler。Unity 的 CPU Usage Profiler 窗口里最常用的排序视图是 Hierarchy也就是按函数调用栈聚合的视图。打开后把顶部的“GC Alloc”列显示出来然后按这个列排序就能看到每帧哪些函数分配了多少字节。这里有一个很重要的操作细节如果你在编辑器里 Profile结果会受到编辑器自身大量分配的影响不能完全代表真机。更好的方式是用真机跑 Development Build开启 Autoconnect Profiler再接上 Profiler 窗口抓数据。抓出来的 GC Alloc 如果显示“每一帧稳定在几 KB 到几十 KB”那就说明热路径上有稳定分配如果能直接看到“GC.Collect”这个入口有巨大耗时那么卡顿根源基本可以锁定。还有一个进阶工具是 Unity 的 Memory Profiler 包它可以看到托管堆里的对象存活情况、内存碎片、以及每个对象被分配时的调用栈。排查“某个对象一直没被释放”这类问题时非常有用。记忆里我见过一个项目主城界面每帧分配 1.5MB找了半天发现是一段网络协议解析代码在反复创建byte[]。Memory Profiler 一照调用栈清清楚楚。2.4 常用止血手段处理 GC 有几种主流手段。第一种是缓存和复用尤其对字符串和数组来说效果立竿见影。比如上面的分数文本可以先判断数值是否真的变了再决定要不要更新 UIprivate int _lastScore int.MinValue; void UpdateScore(int score) { if (_lastScore score) return; _lastScore score; scoreText.text score.ToString(); }这样一来分数不变时连文本赋值都不会发生既能减少 GC又能避免 Canvas 被反复打脏。第二种是对象池。对于经常创建和销毁的 MonoBehaviour、普通类、项目符号特效等缓存到池里重复使用。注意这里的“对象”不仅指游戏对象也包括 List、Dictionary、StringBuilder 这类容器。频繁操作 List 时正确姿势是创建一次用Clear()复用而不是每次 new 一个。第三种是减少闭包和委托分配。事件系统、协程、协程等待对象、匿名方法这些在 Unity 里很容易让开发者无意识分配。不是在所有情况下都绝对不能碰但要记住一点热路径上每帧执行的代码优先级永远是“不分配 少分配 回收后再说”。我个人做性能优化时有个硬性习惯把每帧的 GC Alloc 当作 CPU 帧时间的一部分来预算。比如目标平台是 60 帧CPU 预算 10ms那 GC Alloc 我会控制在每帧 1KB 以下。别小看这个数字只要热路径上没有字符串拼接和 LINQ大多数项目并不难做到。3. Draw CallCPU 与 GPU 之间的谈判成本“Draw Call 越多越卡”这句话几乎所有 Unity 开发者都听过。但它到底为什么这么吃 CPU很多人并没有细想。其实 Draw Call 的本质不是 GPU 干不了这么多活而是 CPU 在提交渲染命令时报给 GPU 的过程本身很昂贵。3.1 一次 Draw Call 到底贵在哪现代图形 API 里一次 Draw Call 意味着 CPU 需要检查材质、Shader、纹理、混合状态、深度状态、透明排序然后生成一条渲染命令写进命令缓冲区最后再通知 GPU 去执行。如果一帧里有 2000 个 Draw Call那 CPU 就要重复做 2000 次状态切换和命令提交。这里最贵的部分不是“提交”本身而是状态切换前后的资源绑定和校验。你可以把 GPU 想象成一条流水线CPU 是工头。工头每次换一种物料、换一台机器都要重新协调先停下当前机器调整参数再启动下一台。状态切换次数越多工头越忙工人反而可能在一边闲着等命令。在 Unity 里我们能直观看到的数字就是 Frame Debugger 或 Profiler 里的 Draw Call 数量。对移动端来说太多小型 Mesh 的批量渲染会让 CPU 的渲染提交时间追平甚至超过 GPU 的实际渲染时间。常见情况是GPU 帧时间只有 5msCPU 光是提交 Draw Call 就花了 4ms整个帧时间直接被 CPU 拖住。3.2 三种合批方案选型对照Unity 提供了几套合批方案很多项目用错不是因为不了解原理而是没有根据项目和管线来选。我把它们的核心特点整理成了表格方案原理优点缺点/坑Dynamic Batching运行时把符合条件的多个小 Mesh 拼成一个批次不用预处理实现简单仅限小顶点数 MeshCPU 也要额外计算合批移动端收益不稳定Static Batching编译期/运行前把静态物体合并成一个大 Mesh合批彻底Draw Call 下降最明显增加内存占用且只对标记为 Static 的物体生效SRP Batcher在 SRP 渲染管线里缓存材质属性复用渲染状态合批效果好不同 Mesh 但同材质兼容性好需要 URP/HDRP且 Shader 必须兼容 SRP Batcher现在的 URP 项目我基本都默认开 SRP Batcher。它和传统合批不太一样它不要求所有物体用同一个 Mesh而是尽量复用材质属性的绑定状态。只要 Shader 兼容且材质属性没有剧烈变化SRP Batcher 可以把大量 Draw Call 的 CPU 提交成本压得很低。但要注意一个陷阱很多项目开了 SRP BatcherDraw Call 数字还是高原因是场景里存在大量MaterialPropertyBlock的使用。MaterialPropertyBlock确实能避免实例化材质但它一旦修改了属性也会破坏一部分 SRP Batcher 的缓存状态。所以不是不能用而是不要每帧无脑改。3.3 别只盯着 Draw Call 数字做优化复盘时我经常看到有人把“Draw Call 从 1500 降到 300”当作成功标准。这个数字当然有意义但要警惕为了降 Draw Call 而把所有小物体合并成一个超大 Mesh可能让内存暴涨、裁剪失效GPU 反而因为要画一个超复杂的模型而变慢。所以更务实的做法是看 CPU 帧时间里的渲染提交耗时。Draw Call 只是一个过程指标真正影响发热和帧率的是 CPU 的工作量。你可以用 Profiler 里 Rendering 相关的采样观察BatchRenderer.Flush、RenderForwardInternal这类函数的耗时再结合 Frame Debugger 看当前场景的批次分布。如果 Draw Call 数量高但 CPU 渲染提交时间很低那不一定非要继续猛削如果 CPU 时间已经顶到 5ms 以上那肯定要动手。在移动端我做优化时目标通常不是“Draw Call 降到多少多少”而是“CPU 渲染提交时间控制在 4ms 以内”。越低越好但也要留一点余量给复杂度更高的场景。因为发热优化讲的是整体功耗不是一个数字。4. Canvas 重建UI 开销里最容易被忽略的一块如果说 GC 和 Draw Call 是“大家都认识但经常找错方向”那 Canvas 重建就是“很多人根本没把它当 CPU 开销”。UI 在发热问题里常常被忽略因为 UI 看起来不占 GPU也不需要跑复杂逻辑。但实际上UGUI 的 Canvas 重建机制可以轻松吃掉 5ms 以上的主线程时间。4.1 为什么改一个 Text 会影响整块 CanvasUGUI 的渲染不是每个 UI 元素独立来的而是先把同一块 Canvas 下的所有元素合并成若干个渲染批次再交给渲染管线。这个合并过程在 CPU 上完成称为 Canvas 的批次重建。问题是批次重建不是只处理你修改的那个元素而是要把这块 Canvas 里所有可见的 UI 几何重新整理一遍。所以经常出现的情况是HUD 里有一个每帧变化的血量数字数字本身只有几十个顶点但因为它在整块 HUD Canvas 中间Unity 需要把整块 Canvas 的顶点缓冲、索引缓冲、材质顺序全部重新计算。我亲眼见过一个只有几个 Text 和 Slider 的界面因为某个 Text 每帧刷新Canvas.SendWillRenderCanvases 一项就占掉 7ms 主线程时间。这还没算布局重建。LayoutGroup、ContentSizeFitter 这类布局组件更夸张某个子元素尺寸一变它要通知父节点重新计算位置和尺寸父节点再影响子节点一层层传下去最后可能整个 UI 树的 RectTransform 都重算一遍。你把这种性能和“每帧改一下文本”叠加在一起CPU 不炸才怪。4.2 常见每帧脏 Canvas 的来源结合我带项目的经验最常见的几类“热 UI”问题如下第一类是每帧刷新文本。分数、计时器、网络延迟、剩余人数这些信息如果没有变化判断就每帧给 Text 赋值。赋值动作必然打脏 Canvas哪怕文本内容完全没变也可能触发重建。解决办法就是我在 GC 那一节讲的先判断值有没有变化再决定要不要赋。第二类是每帧移动 RectTransform。很多弹窗、轮盘、跟随效果的 UI 会在 Update 里修改anchoredPosition。RectTransform 的位移一旦改动Unity 需要更新这块 Canvas 里相关几何体并重新做批次。即使只有一个元素在动依赖的 Canvas 也可能整体重建。第三类是动态布局。ContentSizeFitter配合VerticalLayoutGroup是动态列表的常用组合但也是性能大杀器。子项一变多布局组要重新计算所有子项的位置然后所有子项的 RectTransform 都会变最终整块 Canvas 打脏。对排行榜、聊天列表这类频繁刷新的界面一定要特别小心。第四类是频繁 SetActive 和 SetParent。UI 元素的显隐、父子关系变化会直接影响 Canvas 的批次结构Unity 必须重新合并可见元素。一个经常开关的提示框如果被反复 SetActive每次操作都可能触发额外重建。4.3 Profiler 和 Frame Debugger 里怎么看 Canvas要量化 Canvas 重建的开销优先看 Unity Profiler 里的 UI 相关模块。打开 Profiler 后在 CPU Usage 模块的时间线里如果看到Canvas.SendWillRenderCanvases或Canvas.BuildBatch有持续耗时那就说明 Canvas 重建正在占用主线程。Frame Debugger 也能帮上忙它虽然主要用来查看 Draw Call但你在事件列表里能找到 Canvas 相关的重建条目能看到这个 Canvas 下一共有多少个批、多少顶点。把 Profiler 和 Frame Debugger 结合来看基本可以判断是批次太多导致 CPU 提交压力大还是 UI 每帧被打脏导致重建压力大。我常用的一个简单方法是把场景里的动态 UI 和静态 UI 分开成两块 Canvas分别用 Profiler 观察它们的重建耗时。如果动态 Canvas 单独跑也占用很高那问题就在动态元素本身如果静态 Canvas 在没有任何 UI 变化时也频繁重建那多半是代码里有人在偷偷改静态 UI 的属性。4.4 我的常用 Canvas 治理清单治理 Canvas 重建我的顺序是先减少每帧脏 UI再拆分 Canvas最后才考虑合并 Canvas。第一点所有每帧刷新的文本和滑块先做值缓存。比如血量文本只在血量变化时写一次进度条 Slider 只在收到新值时设置value。这招能在源头消灭大量无意义的重建。第二点拆分 Canvas。动态 HUD、静态背景、弹出面板尽量放到不同的 Canvas 下。这样动态 HUD 每帧重建时静态背景和弹出面板不受影响。但也要注意Canvas 不是分得越细越好。每块 Canvas 都有独立的排序和批次拆太碎会增加 Draw Call 和排序开销。我的经验是一个界面拆 2 到 3 块就够别一个 Text 一块。第三点动态列表不要过度依赖布局组件。如果列表项尺寸固定优先用代码直接控制位置而不是让 LayoutGroup 每帧重新排列。内容长度不固定的文本场景宁可把动态区域单独放一块 Canvas把布局计算的影响范围控制住。第四点避开同一帧里集中改动大量 UI。比如活动开始瞬间要刷几十个文本和图标尽量把操作拆到几帧去执行或者提前准备好一次性设置避免一帧内触发多轮布局和图形重建。5. 实战排查从 Profiler 定位到修复的完整链路理论说再多最后还是要落到实际项目里。我把一个典型的 CPU 发热排查流程完整走一遍你会发现大部分问题都有章可循。5.1 先定指标再动代码每次开始优化之前先确定目标设备、目标帧率和 CPU 预算。比如目标是一台中端 Android 手机跑 60 帧那么单帧总预算 16.6ms。考虑到系统开销和渲染线程我通常会留出至少 2ms 余量CPU 主线程预算定在 10ms 左右GPU 预算定在 8ms 左右。这样即便有些帧出现小波动也不至于立刻掉帧。然后真机跑同一个场景至少五分钟等到手机开始发热、帧率明显下降后再抓 Profiler。很多人上来就在编辑器里点 Play看几秒数据就开始动代码这是不对的。发热优化必须看“持续负载”不是看“冷机峰值性能”。5.2 一次典型的 CPU 发热定位过程假设有个项目启动五分钟之后后盖烫手帧率降到 30 帧以下。我用真机 Development Build 抓 Profiler先看 CPU Usage 的时间线主线程帧时间已经到 22ms而 GPU 只有 6ms结论很明确瓶颈在 CPUGPU 先放一边。接着看 CPU Hierarchy 视图按耗时排序排在最前面的几个函数大概是这样采样名称每帧耗时说明Canvas.SendWillRenderCanvases6.8ms界面每帧被频繁打脏可能是文本或位置在 Update 里改GC.Collect每 120 帧出现一次 45ms 长暂停托管堆里有稳定分配来源需要看 GC Alloc 列BatchRenderer.Flush3.5ms场景 Draw Call 数量偏高渲染提交成本太大自定义脚本 Update2.1ms逻辑本身不算重但要继续看有没有分配看到这个分布排查方向就清晰了。先处理 GC Alloc按 GC Alloc 列排序找到每帧分配最多的函数大概率是字符串拼接或 LINQ。然后把 Canvas 重建来源单独抓一遍看是哪个脚本在碰 Text 和 RectTransform。5.3 用自定义采样缩小范围很多时候 Profiler 里的函数名不够直观Unity 自带的一些引擎内部函数看不懂也无妨。我们可以用Profiler.BeginSample和Profiler.EndSample给关键逻辑加上标记这样 Profiler 图上就能直接显示这一段代码的耗时。比如怀疑 HUD 刷新是重灾区就在刷新函数外面包一层void RefreshHUD() { Profiler.BeginSample(RefreshHUD); scoreText.text _score.ToString(); hpSlider.value _hp; Profiler.EndSample(); }打包跑真机后在 Profiler 的 Hierarchy 视图里就能看到RefreshHUD这一行连它内部的子采样也能展开。如果你发现RefreshHUD才 1ms但紧随其后 Canvas.SendWillRenderCanvases 多了 5ms那问题就清晰了不是刷新函数本身慢而是它每帧打脏了 Canvas 导致的连锁反应。还有一个实用技巧在真机上抓数据时设置 Profiler 的“Frame Count”或者手动标记开始/结束节点只录制从发热那一段时间的数据。这样能避免抓到冷机阶段的无意义数据也让回放时更容易聚焦到问题帧。5.4 从数据到改动三个常用止血方案拿到数据之后常见的改动方向一般是三类。第一类消灭每帧分配。把 GC Alloc 的来源函数全部改一遍字符串用缓存LINQ 改循环临时数组改复用。改完后再跑 Profiler目标是每帧 GC Alloc 降到 0 或 1KB 以下。第二类减少 Canvas 打脏。给文本赋值加变化判断把动态 UI 和静态 UI 拆到不同 Canvas布局组件能不用就不用。改完后再看Canvas.SendWillRenderCanvases的耗时通常能直接降一半以上。第三类控制 Draw Call。打开 Frame Debugger 看批次分布优先检查是不是同材质但没合批或者有没有大量小模型没有标记 Static。该合批的合批该用图集的用图集该开 SRP Batcher 的开 SRP Batcher。我印象很深的一次优化是把主界面的帧时间从 24ms 压到了 11ms方案并不复杂修掉一个每帧分配几十 KB 的日志字符串把排行榜界面拆成独立 Canvas再把 1200 个 Draw Call 降到 500。全程没用特别高深的技术只是把 Profiler 的数据一项项落实到了代码改动上。6. 发烫优化不能只做“一次急救”除非项目只是临时要上架否则性能优化一定要变成长期机制。发热问题最容易复发因为版本迭代会不断有人往场景里加 UI、加特效、加新的逻辑任何一个改动都可能让 CPU 预算再次超支。6.1 给 CPU 留预算而不是留感觉我在项目里推动过一种“帧预算看板”的做法把目标帧率下的单帧时间拆成几个大项比如脚本逻辑预算 3msUI 预算 2ms渲染提交预算 4ms物理预算 1msBufferc/Culling 预算 2ms。每项挂一个 Profiler 采样性能测试时直接看每个预算有没有超支。这个做法最大的好处是当某帧变慢时能立刻知道是哪项预算透支了而不是凭感觉猜。比如 UI 预算明明只有 2ms某个版本却跑到了 5ms那说明 UI 这边引入了脏 Canvas 或者复杂布局代码审查时也能据此重点检查。6.2 建立“真机性能回归测试点”优化只是第一步防退化才是长期工程。有条件的话整理出一套固定场景的回归清单主城、战斗、结算、排行榜、商城每个场景跑固定时长在固定设备上记录主线程时间、GC Alloc、Draw Call、Canvas 重建耗时这几个核心指标。这套指标可以手动记录也可以做成自动化。手动记录时注意统一设备状态比如屏幕亮度、飞行模式、是否充电、后台进程否则数据波动会很大。自动化则可以在 CI 里接 Unity Test Framework 配合 Profiler 采集每次合入代码后跑一遍基准场景超过阈值就自动失败。我这里特别强调真机因为编辑器性能数据真不能代表最终用户体验。同一段代码在编辑器里可能因为 GC 回收时机不同表现完全两样。哪怕只是几台不同档位的真机也能暴露出一大堆 PC 上看不出来的问题。6.3 别只盯主线程但 CPU 永远优先移动端发热优化不是只做 CPU 就完事GPU 发热、资源带宽、分辨率、特效复杂度都会影响功耗。但如果你的项目还没有建立 CPU 预算机制我建议先从这里开始。因为 GC、Draw Call、Canvas 重建这三样东西是最容易被“感觉”掩盖、又最能在短时间内见效的优化点。我自己的体会是CPU 发热优化中最大的敌人不是技术难度而是信息不透明。当你能用 Profiler 把每毫秒的开销都翻出来时很多问题根本不需要“神奇技巧”只需要老老实实改代码。改完之后后盖温度降没降、帧率稳没稳都是一眼能看到的结果。最后再分享一个小技巧优化后不要只在编辑器里看一眼帧率要戴上真机连续玩五分钟以上同时观察 CPU 频率曲线和机身温度。如果主线程时间降下来了但 CPU 频率还一直顶在最高档那说明还有一些后台任务在持续吃电。把这一层也排干才是真正的“发烫优化”。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门