⚡ Unity CPU 性能优化完全手册:从脚本到多线程的性能提升之路
💡 CPU 优化的价值 :
游戏逻辑复杂,CPU 成了性能瓶颈?
想用 Job System 和 Burst,却不知道从何入手?
主线程压力太大,如何利用多核 CPU?
协程、多线程、Job System 该怎么选?
这篇文章! 将系统讲解 Unity CPU 优化技术,从基础脚本优化到 Job System,让性能提升 10 倍!
一、CPU 性能分析 1.1 Unity Profiler 使用 打开 Profiler :
1 Window → Analysis → Profiler
关键指标 :
指标
说明
理想值
CPU Usage
主线程占用
< 16.6ms (60fps)
Script Lateupdate
脚本执行时间
越低越好
Physics.Simulate
物理模拟时间
< 3ms
GarbageCollector
GC 造成的卡顿
最小化
Rendering
GPU 等待时间
平衡 CPU/GPU
1.2 CPU 瓶颈识别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ┌─────────────────────────────────────────────────────────────────────────┐ │ CPU 瓶颈类型识别 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 瓶颈类型: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ CPU Bound │ 主线程脚本耗时过长 │ │ │ │ │ → 优化算法,减少计算 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Rendering Bound │ 渲染等待时间长 │ │ │ │ │ → 降低Draw Call,优化几何体 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ GC Alloc │ 频繁内存分配 │ │ │ │ │ → 减少堆分配,使用对象池 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Physics Heavy │ 物理模拟耗时 │ │ │ │ │ → 减少碰撞体,简化物理 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘
二、脚本优化基础 2.1 缓存组件引用 ❌ 错误做法 :每帧查找组件
1 2 3 4 5 void Update (){ var rb = GetComponent<Rigidbody>(); rb.velocity = Vector3.forward * speed; }
✅ 正确做法 :缓存组件引用
1 2 3 4 5 6 7 8 9 10 11 private Rigidbody rb;void Awake (){ rb = GetComponent<Rigidbody>(); } void Update (){ rb.velocity = Vector3.forward * speed; }
2.2 避免字符串拼接 ❌ 错误做法 :
1 2 3 4 5 void Update (){ string message = "Score: " + score + ", Time: " + Time.time; Debug.Log(message); }
✅ 正确做法 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private System.Text.StringBuilder sb = new System.Text.StringBuilder(256 );void Update (){ sb.Clear(); sb.Append("Score: " ).Append(score).Append(", Time: " ).Append(Time.time); Debug.Log(sb.ToString()); } private float logInterval = 1f ;private float logTimer;void Update (){ logTimer += Time.deltaTime; if (logTimer >= logInterval) { Debug.Log($"Score: {score} , Time: {Time.time} " ); logTimer = 0 ; } }
2.3 静态属性缓存 ❌ 错误做法 :
1 2 3 4 5 6 7 8 void Update (){ for (int i = 0 ; i < 1000 ; i++) { transform.position += Vector3.forward * Time.deltaTime * speed; } }
✅ 正确做法 :
1 2 3 4 5 6 7 8 void Update (){ float dt = Time.deltaTime; for (int i = 0 ; i < 1000 ; i++) { transform.position += Vector3.forward * dt * speed; } }
2.4 减少空引用检查 ❌ 错误做法 :频繁使用 Find
1 2 3 4 5 6 7 8 void Update (){ GameObject enemy = GameObject.Find("Enemy" ); if (enemy != null ) { } }
✅ 正确做法 :使用引用或对象池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private GameObject enemy;void Start (){ enemy = GameObject.Find("Enemy" ); } void Update (){ if (enemy != null ) { } }
三、Update 优化策略 3.1 降低调用频率 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using UnityEngine;public class BatchProcessing : MonoBehaviour { private const int TotalItems = 1000 ; private int currentIndex = 0 ; private int itemsPerFrame = 50 ; void Update () { ProcessBatch(); } void ProcessBatch () { int endIndex = Mathf.Min(currentIndex + itemsPerFrame, TotalItems); for (int i = currentIndex; i < endIndex; i++) { ProcessItem(i); } currentIndex = endIndex >= TotalItems ? 0 : endIndex; } void ProcessItem (int index ) { } }
3.2 按距离剔除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using UnityEngine;using System.Collections.Generic;public class DistanceCulling : MonoBehaviour { public List<Enemy> enemies = new List<Enemy>(); public Transform player; public float maxDistance = 50f ; void Update () { float sqrDistThreshold = maxDistance * maxDistance; foreach (var enemy in enemies) { float sqrDist = (enemy.transform.position - player.position).sqrMagnitude; if (sqrDist < sqrDistThreshold) { enemy.UpdateAI(); } } } } class Enemy { public Transform transform; public void UpdateAI () { } }
3.3 使用协程替代 Update 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 using UnityEngine;using System.Collections;public class CoroutineVsUpdate : MonoBehaviour { void Start () { StartCoroutine(InputCoroutine()); StartCoroutine(AICoroutine()); StartCoroutine(UICoroutine()); } IEnumerator InputCoroutine () { while (true ) { CheckInputs(); yield return null ; } } IEnumerator AICoroutine () { while (true ) { ProcessAI(); yield return new WaitForSeconds (0.1f ) ; } } IEnumerator UICoroutine () { while (true ) { UpdateUI(); yield return new WaitForSeconds (0.2f ) ; } } void CheckInputs () { } void ProcessAI () { } void UpdateUI () { } }
四、多线程优化 4.1 Thread 与 DispatchQueue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 using UnityEngine;using System.Threading;using System.Collections.Generic;public class ThreadingExample : MonoBehaviour { private Thread workerThread; private Queue<System.Action> taskQueue = new Queue<System.Action>(); private bool isRunning = true ; private readonly object queueLock = new object (); void Start () { workerThread = new Thread(WorkerLoop); workerThread.IsBackground = true ; workerThread.Start(); } void Update () { } void OnDestroy () { isRunning = false ; if (workerThread != null && workerThread.IsAlive) { workerThread.Join(); } } public void EnqueueTask (System.Action task ) { lock (queueLock) { taskQueue.Enqueue(task); } } private void WorkerLoop () { while (isRunning) { System.Action task = null ; lock (queueLock) { if (taskQueue.Count > 0 ) task = taskQueue.Dequeue(); } task?.Invoke(); Thread.Sleep(1 ); } } }
4.2 UnityMainThreadDispatcher 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 using UnityEngine;using System.Collections.Generic;using System.Threading;using System;public class UnityMainThreadDispatcher : MonoBehaviour { private static UnityMainThreadDispatcher instance; private readonly Queue<System.Action> executionQueue = new Queue<System.Action>(); public static UnityMainThreadDispatcher Instance { get { if (instance == null ) { GameObject go = new GameObject("UnityMainThreadDispatcher" ); instance = go.AddComponent<UnityMainThreadDispatcher>(); DontDestroyOnLoad(go); } return instance; } } void Update () { lock (executionQueue) { while (executionQueue.Count > 0 ) { executionQueue.Dequeue().Invoke(); } } } public void Enqueue (System.Action action ) { lock (executionQueue) { executionQueue.Enqueue(action); } } public static void RunOnMainThread (System.Action action ) { Instance.Enqueue(action); } } public class WorkerThreadExample { private Thread worker; void Start () { worker = new Thread(DoWork); worker.Start(); } void DoWork () { int result = HeavyCalculation(); UnityMainThreadDispatcher.RunOnMainThread(() => { Debug.Log($"Result: {result} " ); }); } int HeavyCalculation () { return 42 ; } }
五、Job System 与 Burst 5.1 Job System 基础 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 using UnityEngine;using Unity.Jobs;using Unity.Collections;public class JobSystemExample : MonoBehaviour { private NativeArray<int > numbers; private NativeArray<int > results; void Start () { numbers = new NativeArray<int >(1000 , Allocator.TempJob); results = new NativeArray<int >(1000 , Allocator.TempJob); for (int i = 0 ; i < 1000 ; i++) { numbers[i] = i; } } void Update () { var job = new ProcessNumbersJob { numbers = numbers, results = results }; JobHandle jobHandle = job.Schedule(numbers.Length, 64 ); jobHandle.Complete(); } void OnDestroy () { if (numbers.IsCreated) numbers.Dispose(); if (results.IsCreated) results.Dispose(); } [BurstCompatible(CompileSynchronously = true) ] private struct ProcessNumbersJob : IJobParallelFor { [ReadOnly ] public NativeArray<int > numbers; [WriteOnly ] public NativeArray<int > results; public void Execute (int index ) { results[index] = numbers[index] * 2 ; } } }
5.2 并行 Job 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 using UnityEngine;using Unity.Jobs;using Unity.Collections;using Unity.Burst;public class BurstJobExample : MonoBehaviour { [BurstCompile ] private struct HeavyCalculationJob : IJobParallelFor { [ReadOnly ] public NativeArray<float > input; [WriteOnly ] public NativeArray<float > output; public float multiplier; public void Execute (int index ) { float value = input[index]; for (int i = 0 ; i < 100 ; i++) { value = Mathf.Sin(value ) * Mathf.Cos(value ); value += multiplier; } output[index] = value ; } } public void ScheduleJob (int count ) { var input = new NativeArray<float >(count, Allocator.TempJob); var output = new NativeArray<float >(count, Allocator.TempJob); for (int i = 0 ; i < count; i++) input[i] = i * 0.1f ; var job = new HeavyCalculationJob { input = input, output = output, multiplier = 2f }; JobHandle jobHandle = job.Schedule(count, 64 ); jobHandle.Complete(); input.Dispose(); output.Dispose(); } }
5.3 Job Chain 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 using UnityEngine;using Unity.Jobs;using Unity.Collections;using Unity.Burst;public class JobChainExample : MonoBehaviour { private NativeArray<int > data; void Start () { data = new NativeArray<int >(1000 , Allocator.Persistent); for (int i = 0 ; i < 1000 ; i++) data[i] = i; } void Update () { var initJob = new InitializeJob { data = data }; var processJob = new ProcessJob { data = data }; var finalizeJob = new FinalizeJob { data = data }; JobHandle initHandle = initJob.Schedule(); JobHandle processHandle = processJob.Schedule(data.Length, 64 , initHandle); JobHandle finalizeHandle = finalizeJob.Schedule(processHandle); finalizeHandle.Complete(); } void OnDestroy () { if (data.IsCreated) data.Dispose(); } [BurstCompile ] private struct InitializeJob : IJobParallelFor { public NativeArray<int > data; public void Execute (int index ) { data[index] = index; } } [BurstCompile ] private struct ProcessJob : IJobParallelFor { public NativeArray<int > data; public void Execute (int index ) { data[index] = data[index] * 2 ; } } [BurstCompile ] private struct FinalizeJob : IJob { public NativeArray<int > data; public void Execute () { int sum = 0 ; for (int i = 0 ; i < data.Length; i++) sum += data[i]; data[0 ] = sum; } } }
六、物理优化 6.1 固定时间步长设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using UnityEngine;public class PhysicsOptimization : MonoBehaviour { [Header("物理设置" ) ] [Tooltip("固定更新频率" ) ] public int fixedFrequency = 50 ; [Tooltip("最大允许时间步长" ) ] public float maximumTimeStep = 0.02f ; void Start () { Time.fixedDeltaTime = 1f / fixedFrequency; Physics.maxDeltaTime = maximumTimeStep; } }
6.2 碰撞体优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 using UnityEngine;public class CollisionOptimization : MonoBehaviour { [Header("碰撞设置" ) ] public LayerMask collisionLayers; public float checkInterval = 0.1f ; private float checkTimer; private Collider[] results = new Collider[10 ]; void Update () { checkTimer += Time.deltaTime; if (checkTimer >= checkInterval) { CheckCollisions(); checkTimer = 0 ; } } void CheckCollisions () { int hitCount = Physics.OverlapSphereNonAlloc( transform.position, 5f , results, collisionLayers, QueryTriggerInteraction.Ignore ); for (int i = 0 ; i < hitCount; i++) { HandleCollision(results[i]); } } void HandleCollision (Collider collider ) { } }
6.3 Rigidbody 配置
优化项
设置
效果
Interpolate
禁用(如果不需要平滑插值)
节省 CPU
Collision Detection
Discrete(离散)
减少连续检测开销
Constraints
减少约束数量
简化物理计算
Sleep Mode
启用休眠
减少静态物体计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using UnityEngine;public class RigidbodyOptimization : MonoBehaviour { void Start () { Rigidbody rb = GetComponent<Rigidbody>(); rb.interpolation = RigidbodyInterpolation.None; rb.collisionDetectionMode = CollisionDetectionMode.Discrete; rb.sleepMode = RigidbodySleepMode.StartAwake; } }
七、内存优化 7.1 减少装箱拆箱 ❌ 产生装箱的代码 :
1 2 3 4 5 6 7 8 9 10 11 void ProcessValue (object value ) { }ProcessValue(42 ); ArrayList list = new ArrayList(); list.Add(42 ); int value = (int )list[0 ]; string text = "Value: " + 42 ;
✅ 避免装箱的代码 :
1 2 3 4 5 6 7 8 9 10 11 void ProcessValue <T >(T value ) where T : struct { }ProcessValue(42 ); List<int > list = new List<int >(); list.Add(42 ); int value = list[0 ];string text = $"Value: {42 } " ;
7.2 对象池实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 using UnityEngine;using System.Collections.Generic;public class ObjectPool <T > where T : class , new (){ private readonly Stack<T> pool = new Stack<T>(); private readonly System.Action<T> resetAction; private readonly int maxSize; public ObjectPool (int initialSize = 10 , int maxSize = 100 , System.Action<T> resetAction = null ) { this .maxSize = maxSize; this .resetAction = resetAction; for (int i = 0 ; i < initialSize; i++) { pool.Push(new T()); } } public T Get () { if (pool.Count > 0 ) { return pool.Pop(); } return new T(); } public void Return (T obj ) { if (pool.Count < maxSize) { resetAction?.Invoke(obj); pool.Push(obj); } } public int PooledCount => pool.Count; } public class GameObjectPool { private readonly Stack<GameObject> pool = new Stack<GameObject>(); private readonly GameObject prefab; private readonly int maxSize; private readonly Transform container; public GameObjectPool (GameObject prefab, int initialSize = 5 , int maxSize = 50 ) { this .prefab = prefab; this .maxSize = maxSize; this .container = new GameObject($"Pool_{prefab.name} " ).transform; for (int i = 0 ; i < initialSize; i++) { GameObject obj = Object.Instantiate(prefab, container); obj.SetActive(false ); pool.Push(obj); } } public GameObject Get () { GameObject obj; if (pool.Count > 0 ) { obj = pool.Pop(); } else { obj = Object.Instantiate(prefab, container); } obj.SetActive(true ); return obj; } public void Return (GameObject obj ) { if (pool.Count < maxSize) { obj.SetActive(false ); pool.Push(obj); } else { Object.Destroy(obj); } } public int PooledCount => pool.Count; }
八、性能检查清单 8.1 代码审查清单
类别
检查项
状态
组件引用
缓存 GetComponent 结果
⬜
Update
避免每帧查找对象
⬜
字符串
避免频繁字符串拼接
⬜
协程
使用协程替代高频 Update
⬜
数学
避免在循环中重复计算
⬜
装箱
避免装箱拆箱
⬜
空检查
避免每帧的空引用检查
⬜
物理
降低物理更新频率
⬜
GC
减少 Per-frame GC Alloc
⬜
多线程
使用 Job System 处理并行任务
⬜
8.2 Profiler 检查点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ┌─────────────────────────────────────────────────────────────────────────┐ │ Profiler 检查要点 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. Rendering: │ │ └── 检查 Draw Calls 和 Batches │ │ │ │ 2. Scripts: │ │ └── 查找耗时最长的脚本函数 │ │ │ │ 3. GC Alloc: │ │ └── 找出产生内存分配的代码行 │ │ │ │ 4. Physics: │ │ └── 检查 Physics.Simulate 占比 │ │ │ │ 5. GUI: │ │ └── 检查 UI 重绘和布局重建 │ │ │ │ 6. Animation: │ │ └── 检查 Animation.Update 占比 │ │ │ └─────────────────────────────────────────────────────────────────────────┘
九、总结
主题
要点
分析工具
Unity Profiler + Deep Profile
脚本优化
缓存组件,避免每帧查找
Update 优化
使用协程,降低调用频率
多线程
Thread + Dispatcher 或 Job System
高性能
Job System + Burst Compiler
物理优化
降低频率,简化碰撞体
内存优化
对象池,避免装箱
批量处理
分帧处理大量对象
💡 核心原则 :
先测量再优化
优化最耗时的部分
避免过早优化
考虑使用 Job System 处理并行计算
使用 Burst Compiler 获得原生性能
对象池是减少 GC 的利器
转载请注明来源 ,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com