⚡ 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; // 产生 GC
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
// 方式一:使用 StringBuilder
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++)
{
// Time.deltaTime 每次访问都有开销
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;

/// <summary>
/// 分帧处理示例
/// </summary>
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;

/// <summary>
/// 距离剔除优化
/// </summary>
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;

/// <summary>
/// 协程替代 Update
/// </summary>
public class CoroutineVsUpdate : MonoBehaviour
{
// ❌ 使用 Update
// void Update()
// {
// CheckInputs(); // 每帧调用
// ProcessAI(); // 每帧调用
// UpdateUI(); // 每帧调用
// }

// ✅ 使用协程
void Start()
{
StartCoroutine(InputCoroutine()); // 60fps
StartCoroutine(AICoroutine()); // 10fps
StartCoroutine(UICoroutine()); // 5fps
}

IEnumerator InputCoroutine()
{
while (true)
{
CheckInputs();
yield return null; // 每帧执行
}
}

IEnumerator AICoroutine()
{
while (true)
{
ProcessAI();
yield return new WaitForSeconds(0.1f); // 每0.1秒执行
}
}

IEnumerator UICoroutine()
{
while (true)
{
UpdateUI();
yield return new WaitForSeconds(0.2f); // 每0.2秒执行
}
}

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;

/// <summary>
/// 简单多线程示例
/// </summary>
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();
}
}

/// <summary>
/// 添加任务到工作线程
/// </summary>
public void EnqueueTask(System.Action task)
{
lock (queueLock)
{
taskQueue.Enqueue(task);
}
}

/// <summary>
/// 工作线程循环
/// </summary>
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;

/// <summary>
/// 主线程调度器 - 从工作线程安全地调用 Unity API
/// </summary>
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();
}
}
}

/// <summary>
/// 在主线程执行委托
/// </summary>
public void Enqueue(System.Action action)
{
lock (executionQueue)
{
executionQueue.Enqueue(action);
}
}

/// <summary>
/// 从工作线程调用 Unity API
/// </summary>
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();

// 切换到主线程操作 Unity 对象
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;

/// <summary>
/// Job System 基础示例
/// </summary>
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()
{
// 创建 Job
var job = new ProcessNumbersJob
{
numbers = numbers,
results = results
};

// 调度 Job
JobHandle jobHandle = job.Schedule(numbers.Length, 64);

// 可以在此期间做其他工作

// 等待 Job 完成
jobHandle.Complete();

// 使用结果
// Debug.Log($"First result: {results[0]}");
}

void OnDestroy()
{
if (numbers.IsCreated) numbers.Dispose();
if (results.IsCreated) results.Dispose();
}

/// <summary>
/// Job 结构体
/// </summary>
[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;

/// <summary>
/// Burst 编译的并行 Job
/// </summary>
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;

// 创建并调度 Job
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;

/// <summary>
/// Job 链式依赖
/// </summary>
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()
{
// 创建多个 Job
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);

// 等待所有 Job 完成
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;

/// <summary>
/// 物理更新频率控制
/// </summary>
public class PhysicsOptimization : MonoBehaviour
{
[Header("物理设置")]
[Tooltip("固定更新频率")]
public int fixedFrequency = 50; // 50Hz

[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;

/// <summary>
/// 碰撞检测优化
/// </summary>
public class CollisionOptimization : MonoBehaviour
{
[Header("碰撞设置")]
public LayerMask collisionLayers;
public float checkInterval = 0.1f;

private float checkTimer;
private Collider[] results = new Collider[10];

void Update()
{
// ❌ 错误:每帧检测
// Collider[] hits = Physics.OverlapSphere(transform.position, 5f);

// ✅ 正确:定时检测
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;

/// <summary>
/// Rigidbody 优化配置
/// </summary>
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
// 1. 值类型作为 object 传递
void ProcessValue(object value) { }
ProcessValue(42); // 装箱

// 2. 数组协变
ArrayList list = new ArrayList();
list.Add(42); // 装箱
int value = (int)list[0]; // 拆箱

// 3. 字符串拼接
string text = "Value: " + 42; // 装箱

✅ 避免装箱的代码

1
2
3
4
5
6
7
8
9
10
11
// 1. 使用泛型
void ProcessValue<T>(T value) where T : struct { }
ProcessValue(42);

// 2. 使用 List<T>
List<int> list = new List<int>();
list.Add(42);
int value = list[0];

// 3. 使用 StringBuilder 或字符串插值
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;

/// <summary>
/// 通用对象池
/// </summary>
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;
}

/// <summary>
/// GameObject 对象池
/// </summary>
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