一、Unity GC机制核心问题
1.1 Unity GC特点
-
分代式GC:Unity使用Boehm GC,分为年轻代和老年代
-
自动管理:开发者不直接控制内存释放时机
-
Stop-the-World:GC触发时会阻塞主线程,导致帧率波动
-
托管堆管理:Unity使用双堆系统(托管堆+Native堆)
1.2 主要GC痛点
-
卡顿:每帧超过2ms的GC会导致明显卡顿
-
内存碎片:频繁分配/释放导致堆碎片化
-
预测困难:GC时机难以精确预测
二、GC性能分析与监控
2.1 监控工具使用
csharp
// 1. Unity Profiler(最核心工具)
// 重点关注:GC Alloc、GC.Collect调用、堆大小变化
// 2. 内置API监控
void Update() {
// 监控当前帧GC分配
long memBefore = System.GC.GetTotalMemory(false);
// 你的代码...
long memAfter = System.GC.GetTotalMemory(false);
long allocated = memAfter - memBefore;
if (allocated > 1024 * 1024) { // 超过1MB
Debug.LogWarning($"Large allocation: {allocated / 1024}KB");
}
}
2.2 关键性能指标
-
目标值:每帧GC分配 < 40KB
-
警告值:单次GC时间 > 2ms
-
危险值:堆大小频繁翻倍
三、高频GC问题与优化方案
3.1 字符串操作优化
csharp
// ❌ 差 - 每次拼接都产生新字符串
string result = "";
for (int i = 0; i < 100; i++) {
result += "Item " + i + ","; // 产生GC
}
// ✅ 好 - 使用StringBuilder
StringBuilder sb = new StringBuilder(200); // 预分配容量
for (int i = 0; i < 100; i++) {
sb.Append("Item ");
sb.Append(i);
sb.Append(",");
}
string result = sb.ToString();
// ✅ 更好 - 使用string.Format或插值字符串(编译器优化)
string result = $"Total: {count} items";
// ✅ 最好 - 预定义静态字符串
static class Strings {
public static readonly string ItemFormat = "Item {0}";
}
3.2 装箱与拆箱优化
csharp
// ❌ 差 - 值类型装箱
ArrayList list = new ArrayList();
list.Add(10); // 装箱,产生GC
list.Add(20);
// ✅ 好 - 使用泛型集合
List<int> list = new List<int>();
list.Add(10); // 无装箱
list.Add(20);
// ❌ 差 - 枚举器装箱
foreach (KeyValuePair<int, string> kvp in dict) {
// 每次迭代可能产生装箱
}
// ✅ 好 - 使用结构体版本
public struct KeyValuePair<K, V> {
public K Key;
public V Value;
}
3.3 协程优化
csharp
// ❌ 差 - 每帧分配
IEnumerator Coroutine1() {
while (true) {
yield return null; // 分配WaitForSeconds对象
// 业务逻辑
}
}
// ✅ 好 - 缓存Yield指令
static readonly WaitForSeconds waitOneSecond = new WaitForSeconds(1f);
static readonly WaitForEndOfFrame waitEndOfFrame = new WaitForEndOfFrame();
IEnumerator Coroutine2() {
while (true) {
yield return waitOneSecond; // 复用对象
// 业务逻辑
}
}
// ✅ 更好 - 避免频繁启动协程
public class TimerComponent : MonoBehaviour {
private float timer;
private float interval = 1f;
void Update() {
timer += Time.deltaTime;
if (timer >= interval) {
timer = 0;
// 执行业务逻辑
}
}
}
3.4 LINQ与Lambda优化
csharp
// ❌ 差 - LINQ产生大量GC
var enemies = enemyList.Where(e => e.IsAlive)
.Select(e => e.Name)
.ToList();
// ✅ 好 - 手动循环
List<string> aliveEnemyNames = new List<string>(enemyList.Count);
for (int i = 0; i < enemyList.Count; i++) {
if (enemyList[i].IsAlive) {
aliveEnemyNames.Add(enemyList[i].Name);
}
}
// ✅ 闭包捕获优化
// 避免在频繁调用的方法中使用Lambda捕获外部变量
3.5 数组与集合优化
csharp
// ❌ 差 - 频繁创建数组
void Update() {
Vector3[] points = new Vector3[100]; // 每帧分配
// 使用points...
}
// ✅ 好 - 对象池模式
public class ArrayPool<T> {
private Stack<T[]> pool = new Stack<T[]>();
public T[] Get(int size) {
if (pool.Count > 0) {
return pool.Pop();
}
return new T[size];
}
public void Return(T[] array) {
pool.Push(array);
}
}
// ✅ 使用Unity内置ArrayPool
using UnityEngine.Pool;
var array = ArrayPool<Vector3>.Shared.Rent(100);
// 使用后归还
ArrayPool<Vector3>.Shared.Return(array);
3.6 Unity API调用优化
csharp
// ❌ 差 - 频繁访问属性
void Update() {
for (int i = 0; i < 100; i++) {
transform.position = new Vector3(i, 0, 0); // 可能产生GC
}
}
// ✅ 好 - 批量操作或缓存
private Transform cachedTransform;
private Vector3 tempPosition = Vector3.zero;
void Start() {
cachedTransform = transform;
}
void Update() {
for (int i = 0; i < 100; i++) {
tempPosition.x = i;
cachedTransform.position = tempPosition;
}
}
// ❌ 差 - 频繁使用GetComponent
void Update() {
var renderer = GetComponent<Renderer>(); // 每次调用都有开销
}
// ✅ 好 - 缓存Component引用
private Renderer cachedRenderer;
void Start() {
cachedRenderer = GetComponent<Renderer>();
}
四、高级优化技巧
4.1 结构体设计优化
csharp
// ❌ 差 - 结构体包含引用类型
public struct BadStruct {
public string name; // 引用类型,破坏值类型语义
public int id;
}
// ✅ 好 - 纯值类型结构体
public ref struct GoodStruct {
public int id;
public float value;
// 避免装箱的方法
public override int GetHashCode() {
return id;
}
}
// ✅ 使用readonly struct(C# 7.2+)
public readonly struct ImmutableStruct {
public readonly int Id;
public readonly float Value;
public ImmutableStruct(int id, float value) {
Id = id;
Value = value;
}
}
4.2 自定义对象池系统
csharp
public class GameObjectPool {
private Queue<GameObject> pool = new Queue<GameObject>();
private GameObject prefab;
public GameObjectPool(GameObject prefab, int initialSize) {
this.prefab = prefab;
for (int i = 0; i < initialSize; i++) {
GameObject obj = GameObject.Instantiate(prefab);
obj.SetActive(false);
pool.Enqueue(obj);
}
}
public GameObject Get() {
if (pool.Count > 0) {
GameObject obj = pool.Dequeue();
obj.SetActive(true);
return obj;
}
return GameObject.Instantiate(prefab);
}
public void Return(GameObject obj) {
obj.SetActive(false);
pool.Enqueue(obj);
}
}
// Unity内置对象池(推荐)
using UnityEngine.Pool;
ObjectPool<Bullet> bulletPool;
4.3 手动控制GC时机
csharp
public class GCController : MonoBehaviour {
// 在合适时机手动触发GC
public void TriggerGCAtSafeTime() {
// 场景切换时
// 加载界面显示时
// 玩家死亡时
System.GC.Collect();
Resources.UnloadUnusedAssets();
}
// 增量式GC(Unity 2019+)
void Update() {
// 每帧处理一小部分GC工作
System.GC.Collect(0, GCCollectionMode.Forced, false);
}
}
五、架构级优化策略
5.1 数据导向设计
csharp
// 传统OOP方式(可能产生更多GC)
public class Enemy {
public string name;
public float health;
public Vector3 position;
public void Update() { /* ... */ }
}
// DOD方式(减少GC,更好的缓存局部性)
public class EnemySystem {
private string[] names;
private float[] healths;
private Vector3[] positions;
private bool[] isAlive;
public void UpdateAll() {
for (int i = 0; i < count; i++) {
if (isAlive[i]) {
// 连续内存访问,性能更好
positions[i] += Vector3.forward * Time.deltaTime;
}
}
}
}
5.2 内存分配策略
csharp
// 预分配策略
public class PreAllocatedMemory {
private Vector3[] reusableArray;
private int currentIndex;
public PreAllocatedMemory(int capacity) {
reusableArray = new Vector3[capacity];
}
public void AddData(Vector3 data) {
if (currentIndex < reusableArray.Length) {
reusableArray[currentIndex++] = data;
}
}
public void Clear() {
currentIndex = 0;
// 重用数组,不重新分配
}
}
六、实战检查清单
每帧检查项
-
字符串拼接是否使用StringBuilder?
-
循环中是否避免了LINQ?
-
是否缓存了GetComponent结果?
-
是否使用了对象池?
-
协程Yield指令是否缓存?
代码审查项
-
值类型是否意外装箱?
-
频繁调用的方法是否分配了内存?
-
集合初始容量是否合理设置?
-
是否使用了ref/out参数减少复制?
-
事件回调是否及时注销?
七、性能测试示例
csharp
public class GCTest : MonoBehaviour {
void Update() {
// 测试代码性能
TestMethod1(); // 优化前
TestMethod2(); // 优化后
}
void TestMethod1() {
// 原始实现
var list = new List<Vector3>();
for (int i = 0; i < 1000; i++) {
list.Add(new Vector3(i, i, i));
}
}
void TestMethod2() {
// 优化后
const int count = 1000;
var list = new List<Vector3>(count); // 预分配
var temp = Vector3.zero;
for (int i = 0; i < count; i++) {
temp.x = i; temp.y = i; temp.z = i;
list.Add(temp);
}
}
}
八、总结要点
-
预防优于治理:良好的编码习惯比后期优化更重要
-
工具驱动:始终使用Profiler监控GC情况
-
适度优化:避免过度优化,关注真正的性能瓶颈
-
持续监控:在目标设备上定期进行性能测试
-
团队规范:建立团队统一的GC优化规范
通过以上优化策略,通常可以将GC时间降低60-90%,显著提升游戏流畅度。记住:最好的GC优化是不分配内存 ,其次是复用已分配的内存。
九、易忽略的GC隐患点
9.1 委托与事件监听
csharp
// ❌ 问题:匿名方法/lambda隐式分配内存
void Start() {
button.onClick.AddListener(() => {
Debug.Log("Clicked"); // 每次都会创建新委托对象
});
}
// ✅ 解决方案:缓存委托引用
private UnityEngine.Events.UnityAction cachedClickAction;
void Start() {
cachedClickAction = OnButtonClick;
button.onClick.AddListener(cachedClickAction);
}
void OnButtonClick() {
Debug.Log("Clicked");
}
void OnDestroy() {
// 必须手动移除,否则导致内存泄漏
button.onClick.RemoveListener(cachedClickAction);
}
// ✅ 更好的模式:使用事件ID系统
public class EventSystem : MonoBehaviour {
private static Dictionary<string, Action> events = new Dictionary<string, Action>();
public static void Subscribe(string eventId, Action callback) {
if (!events.ContainsKey(eventId))
events[eventId] = null;
events[eventId] += callback;
}
public static void UnsubscribeAll(string eventId) {
events.Remove(eventId); // 批量清理,避免逐个移除的开销
}
}
9.2 资源加载与卸载
csharp
// ❌ 问题:Resources.Load的隐藏GC
// 即使使用Resources.UnloadUnusedAssets,也可能触发GC
// ✅ 解决方案:Addressables系统(Unity 2018.3+)
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class AssetLoader {
private Dictionary<string, AsyncOperationHandle> loadedAssets =
new Dictionary<string, AsyncOperationHandle>();
public async void LoadAssetAsync<T>(string address) {
if (loadedAssets.ContainsKey(address)) return;
var handle = Addressables.LoadAssetAsync<T>(address);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded) {
loadedAssets[address] = handle;
}
}
public void UnloadAsset(string address) {
if (loadedAssets.TryGetValue(address, out var handle)) {
Addressables.Release(handle);
loadedAssets.Remove(address);
}
}
}
// ✅ AssetBundle加载优化
public class BundleLoader {
private AssetBundleCreateRequest bundleRequest;
private AssetBundleRequest assetRequest;
// 预加载依赖,避免运行时加载卡顿
public IEnumerator PreloadDependencies(string bundleName) {
var dependencies = AssetBundle.GetAllDependencies(bundleName);
foreach (var dep in dependencies) {
var bundle = AssetBundle.LoadFromFileAsync(dep);
yield return bundle;
bundle.assetBundle.Unload(false); // 不卸载实际资源,只释放AssetBundle对象
}
}
}
十、平台特定优化
10.1 iOS平台特殊处理
objectivec
// C#端针对iOS的优化
public class IOSOptimizer {
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void IOS_DisableARCOnThread();
void Start() {
#if UNITY_IOS && !UNITY_EDITOR
// iOS上避免频繁的小对象分配
// 使用固定大小的数组而非List
// 禁用特定线程的ARC(通过Native插件)
#endif
}
}
// Objective-C插件示例
@implementation MemoryManager
+ (void)disableARCForCurrentThread {
// 在非主线程禁用ARC,减少autorelease压力
}
@end
10.2 Android平台内存管理
csharp
public class AndroidMemoryManager : MonoBehaviour {
private AndroidJavaObject activity;
void Start() {
#if UNITY_ANDROID && !UNITY_EDITOR
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
// 触发Android系统的垃圾回收(谨慎使用)
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
// 通知系统进行内存整理
activity.Call("runOnUiThread", new AndroidJavaRunnable(() => {
using (AndroidJavaObject runtime = new AndroidJavaClass("java.lang.Runtime")
.CallStatic<AndroidJavaObject>("getRuntime")) {
runtime.Call("gc");
}
}));
#endif
}
void OnApplicationPause(bool pause) {
if (pause) {
// 应用进入后台时主动清理
Resources.UnloadUnusedAssets();
System.GC.Collect();
}
}
}
十一、高级架构模式
11.1 无GC消息系统
csharp
// 使用结构体+数组实现零分配消息系统
public struct Message {
public int Type;
public int Data1;
public float Data2;
// 最多8个值类型字段
}
public class MessageBus {
private Message[] messageBuffer;
private int head = 0;
private int tail = 0;
private const int BUFFER_SIZE = 1024;
public MessageBus() {
messageBuffer = new Message[BUFFER_SIZE];
}
public bool TryPostMessage(in Message msg) {
int next = (head + 1) % BUFFER_SIZE;
if (next == tail) return false; // 缓冲区满
messageBuffer[head] = msg;
head = next;
return true;
}
public bool TryGetMessage(out Message msg) {
if (tail == head) {
msg = default;
return false;
}
msg = messageBuffer[tail];
tail = (tail + 1) % BUFFER_SIZE;
return true;
}
}
// 使用示例
public class GameSystem : MonoBehaviour {
private MessageBus bus = new MessageBus();
void Update() {
Message msg;
while (bus.TryGetMessage(out msg)) {
ProcessMessage(ref msg); // ref避免复制
}
}
}
11.2 Unity DOTS/ECS 完全避免GC
csharp
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
// ECS系统示例 - 零GC
public class MovementSystem : JobComponentSystem {
private EntityQuery moveQuery;
protected override void OnCreate() {
// 查询所有具有Position和Velocity组件的实体
moveQuery = GetEntityQuery(
ComponentType.ReadWrite<Position>(),
ComponentType.ReadOnly<Velocity>()
);
}
// Burst编译,无GC,SIMD优化
[BurstCompile]
struct MoveJob : IJobChunk {
public float deltaTime;
public ArchetypeChunkComponentType<Position> positionType;
[ReadOnly] public ArchetypeChunkComponentType<Velocity> velocityType;
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
var positions = chunk.GetNativeArray(positionType);
var velocities = chunk.GetNativeArray(velocityType);
// 自动向量化,极高性能
for (int i = 0; i < chunk.Count; i++) {
positions[i] = new Position {
Value = positions[i].Value + velocities[i].Value * deltaTime
};
}
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps) {
var moveJob = new MoveJob {
deltaTime = Time.deltaTime,
positionType = GetArchetypeChunkComponentType<Position>(),
velocityType = GetArchetypeChunkComponentType<Velocity>()
};
return moveJob.Schedule(moveQuery, inputDeps);
}
}
十二、调试与监控增强
12.1 自动化GC检测
csharp
#if DEVELOPMENT_BUILD || UNITY_EDITOR
public class GCDetector : MonoBehaviour {
private long lastTotalMemory;
private int frameCount;
private const int CHECK_INTERVAL = 30; // 每30帧检查一次
void Update() {
if (++frameCount % CHECK_INTERVAL != 0) return;
long currentMemory = System.GC.GetTotalMemory(false);
long allocated = currentMemory - lastTotalMemory;
if (allocated > 1024 * 512) { // 512KB阈值
Debug.LogError($"Large allocation detected: {allocated/1024}KB in {CHECK_INTERVAL} frames");
// 自动触发性能快照
Debug.BeginDeepProfile("GC_Allocation_Spike");
}
lastTotalMemory = currentMemory;
}
// 堆栈跟踪分配源头
public static void TrackAllocation<T>(T obj, int skipFrames = 2) {
#if UNITY_EDITOR
var stackTrace = new System.Diagnostics.StackTrace(skipFrames, true);
Debug.Log($"Allocated {typeof(T).Name} at:\n{stackTrace}");
#endif
}
}
#endif
// 自动注入分配追踪
public class TrackedList<T> : List<T> {
public new void Add(T item) {
#if DEVELOPMENT_BUILD
GCDetector.TrackAllocation(item);
#endif
base.Add(item);
}
}
12.2 运行时内存可视化
csharp
public class MemoryVisualizer : MonoBehaviour {
private struct AllocationRecord {
public string TypeName;
public long Size;
public int Frame;
public string StackTrace;
}
private List<AllocationRecord> records = new List<AllocationRecord>();
private Vector2 scrollPos;
void OnEnable() {
// 订阅Unity日志,捕获GC分配警告
Application.logMessageReceived += OnLogMessage;
}
void OnDisable() {
Application.logMessageReceived -= OnLogMessage;
}
void OnLogMessage(string condition, string stackTrace, LogType type) {
if (type == LogType.Warning && condition.Contains("GC Allocation")) {
records.Add(new AllocationRecord {
TypeName = ExtractTypeName(condition),
Size = ExtractSize(condition),
Frame = Time.frameCount,
StackTrace = stackTrace
});
// 保持最近100条记录
if (records.Count > 100) records.RemoveAt(0);
}
}
void OnGUI() {
GUILayout.BeginVertical(GUI.skin.box);
scrollPos = GUILayout.BeginScrollView(scrollPos, GUILayout.Width(600), GUILayout.Height(300));
foreach (var record in records) {
GUILayout.Label($"[Frame {record.Frame}] {record.TypeName}: {record.Size/1024}KB");
}
GUILayout.EndScrollView();
GUILayout.EndVertical();
}
}
十三、Unity版本特定优化
13.1 Unity 2019+ 增量式GC
csharp
// 启用增量垃圾回收(Unity 2019.1+)
// Player Settings: Scripting Garbage Collection → Incremental
// 手动控制增量GC
public class IncrementalGCController : MonoBehaviour {
private float gcTimeBudget = 1.0f; // 每帧最多1ms用于GC
private float accumulatedTime = 0f;
void Update() {
// 累计需要GC处理的时间
accumulatedTime += Time.unscaledDeltaTime;
// 当累计时间超过阈值时,触发增量GC
if (accumulatedTime > 1.0f) { // 每秒处理一次
float timeSlice = Mathf.Min(gcTimeBudget, accumulatedTime);
// 执行增量GC
System.GC.CollectIncremental(timeSlice * 1000000f); // 转换为纳秒
accumulatedTime = 0f;
}
}
}
13.2 Unity 2021.2+ GC配置
csharp
// 通过启动参数调整GC行为
// 编辑启动参数:-gc-max-time-slice=3 (GC每帧最多3ms)
// 运行时查询和调整GC参数
public class GCTuner : MonoBehaviour {
void Start() {
#if UNITY_2021_2_OR_NEWER
// 获取当前GC模式
var gcMode = System.GarbageCollector.GCMode;
Debug.Log($"Current GC Mode: {gcMode}");
// 动态切换GC模式(谨慎使用)
if (SystemInfo.systemMemorySize < 2048) { // 低内存设备
System.GarbageCollector.GCMode = GarbageCollector.Mode.Disabled;
// 改为手动控制GC时机
InvokeRepeating("ManualGC", 30f, 30f); // 每30秒GC一次
}
#endif
}
void ManualGC() {
// 在加载界面或过场动画时触发
if (!isGameplayActive) {
System.GC.Collect();
Resources.UnloadUnusedAssets();
}
}
}
十四、实战中的取舍与权衡
14.1 可读性 vs 性能
csharp
// 根据项目阶段选择不同策略
public class CodeStyleSelector {
// 开发阶段:保持可读性
public void DeveloperFriendlyCode() {
var filtered = items.Where(item => item.IsValid)
.Select(item => item.Value)
.ToList(); // 虽然有一点GC,但代码清晰
}
// 发布阶段:优化性能
public void ProductionOptimizedCode() {
// 预分配List
var filtered = new List<float>(items.Count);
for (int i = 0; i < items.Count; i++) {
if (items[i].IsValid) {
filtered.Add(items[i].Value);
}
}
}
}
// 使用条件编译
#if UNITY_EDITOR || DEVELOPMENT_BUILD
// 开发版本:使用方便的API,包含更多检查
#else
// 发布版本:使用优化版本
#endif
14.2 内存 vs CPU
csharp
// 内存换CPU:对象池的权衡
public class BalancingPool : MonoBehaviour {
private bool usePooling = true;
void Update() {
if (usePooling) {
// 对象池:减少GC,增加内存占用
var obj = ObjectPool.Get();
// 使用...
ObjectPool.Return(obj);
} else {
// 即时创建:更多GC,更少内存
var obj = Instantiate(prefab);
// 使用...
Destroy(obj, 1f);
}
}
// 根据平台动态调整策略
void AdjustStrategy() {
#if UNITY_IOS || UNITY_ANDROID
// 移动平台:偏向减少GC,容忍更高内存
usePooling = true;
#else
// PC平台:可以接受一些GC,节省内存
usePooling = SystemInfo.systemMemorySize < 8192; // 8GB以下使用池
#endif
}
}
十五、团队协作最佳实践
15.1 GC优化代码规范
csharp
// 团队代码规范示例
/*
GC优化编码规范 V1.0
1. 禁止在Update/FixedUpdate中使用:
- new 关键字(除值类型外)
- LINQ (Where, Select, First等)
- 字符串拼接(用StringBuilder)
- GetComponent(需缓存)
2. 必须使用:
- 对象池(重复创建对象时)
- 预分配集合容量
- 结构体替代小类
- 静态只读字符串
3. 代码审查检查点:
- Profiler中GC.Alloc列
- 是否使用了ref/readonly修饰符
- 事件监听是否及时移除
- 协程Yield指令是否缓存
*/
// 使用自定义Attribute标记需要GC优化的方法
[System.AttributeUsage(System.AttributeTargets.Method)]
public class GCriticalAttribute : System.Attribute {
public int MaxAllocation { get; private set; }
public GCriticalAttribute(int maxBytes = 1024) {
MaxAllocation = maxBytes;
}
}
// 代码分析工具检查
[GCritical(512)]
void UpdatePlayerPosition() {
// 这个方法会被代码分析工具检查GC分配
}
15.2 自动化性能测试
csharp
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using NUnit.Framework;
public class GCPerformanceTests {
[Test]
public void UpdateLoop_AllocationUnder40KB() {
var testObject = new GameObject("GCTest").AddComponent<PlayerController>();
// 预热
for (int i = 0; i < 10; i++) {
testObject.SimulateUpdate();
}
// 正式测试
long totalAllocated = 0;
int testFrames = 100;
for (int i = 0; i < testFrames; i++) {
long before = System.GC.GetTotalMemory(false);
testObject.SimulateUpdate();
long after = System.GC.GetTotalMemory(false);
totalAllocated += (after - before);
}
float avgPerFrame = totalAllocated / (float)testFrames;
Assert.LessOrEqual(avgPerFrame, 40 * 1024,
$"平均每帧分配 {avgPerFrame/1024:F1}KB,超过40KB限制");
}
[MenuItem("Tests/Run GC Performance Tests")]
static void RunGCTests() {
var results = new TestRunner();
results.RunAllTests();
// 生成报告
GenerateGCReport();
}
}
#endif
关键补充总结
-
Unity版本差异:
-
2018.3+:使用Addressables替代Resources
-
2019.1+:启用增量式GC
-
2020.1+:Burst编译器更成熟
-
2021.2+:GC模式可动态调整
-
-
平台特殊性:
-
iOS:关注Autorelease池
-
Android:注意Java堆与Native堆交互
-
主机平台:内存限制严格,需精细控制
-
-
监控层次化:
-
开发期:Unity Profiler + 自定义检测
-
测试期:自动化性能测试
-
线上期:轻量级采样上报
-
-
团队流程:
-
代码审查加入GC检查项
-
性能预算制度(每系统/每帧)
-
定期性能回归测试
-
记住:没有绝对的优化规则,只有适合当前项目的权衡。在高性能要求的场景(VR、竞技游戏)中需要极致的GC控制,而在一些休闲或单机游戏中,适当的GC是可以接受的。关键是通过数据驱动决策,用Profiler说话。