Unity GC实战优化总结

一、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;
        // 重用数组,不重新分配
    }
}

六、实战检查清单

每帧检查项

  1. 字符串拼接是否使用StringBuilder?

  2. 循环中是否避免了LINQ?

  3. 是否缓存了GetComponent结果?

  4. 是否使用了对象池?

  5. 协程Yield指令是否缓存?

代码审查项

  1. 值类型是否意外装箱?

  2. 频繁调用的方法是否分配了内存?

  3. 集合初始容量是否合理设置?

  4. 是否使用了ref/out参数减少复制?

  5. 事件回调是否及时注销?

七、性能测试示例

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);
        }
    }
}

八、总结要点

  1. 预防优于治理:良好的编码习惯比后期优化更重要

  2. 工具驱动:始终使用Profiler监控GC情况

  3. 适度优化:避免过度优化,关注真正的性能瓶颈

  4. 持续监控:在目标设备上定期进行性能测试

  5. 团队规范:建立团队统一的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

关键补充总结

  1. Unity版本差异

    • 2018.3+:使用Addressables替代Resources

    • 2019.1+:启用增量式GC

    • 2020.1+:Burst编译器更成熟

    • 2021.2+:GC模式可动态调整

  2. 平台特殊性

    • iOS:关注Autorelease池

    • Android:注意Java堆与Native堆交互

    • 主机平台:内存限制严格,需精细控制

  3. 监控层次化

    • 开发期:Unity Profiler + 自定义检测

    • 测试期:自动化性能测试

    • 线上期:轻量级采样上报

  4. 团队流程

    • 代码审查加入GC检查项

    • 性能预算制度(每系统/每帧)

    • 定期性能回归测试

记住:没有绝对的优化规则,只有适合当前项目的权衡。在高性能要求的场景(VR、竞技游戏)中需要极致的GC控制,而在一些休闲或单机游戏中,适当的GC是可以接受的。关键是通过数据驱动决策,用Profiler说话。

相关推荐
玩泥巴的1 小时前
深入理解飞书 Webhook 签名验证:一次踩坑到填坑的完整记录
c#·.net·飞书
FuckPatience5 小时前
C# SqlSugar+SQLite: 无法加载 DLL“e_sqlite3”: 找不到指定的模块
开发语言·c#
HelloRevit5 小时前
Windows Server SMB 共享文件 回收站
windows·c#
曹牧6 小时前
C#:ToDouble
开发语言·c#
yongui478346 小时前
使用C#实现Excel实时读取并导入SQL数据库
数据库·c#·excel
阿蒙Amon7 小时前
C#每日面试题-简述匿名方法
java·面试·c#
波波0077 小时前
C# 中静态类的正确与错误用法
c#
阿蒙Amon7 小时前
C#每日面试题-简述匿名类型
开发语言·c#
jghhh017 小时前
C#中实现不同进程(EXE)间通信的方案
java·单例模式·c#