You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化生命周期未定义的DamageData类内存分配,降低GC压力

问题根源分析

你当前的核心问题是只池化了内部的DamageDataInternal,但DamageData主类仍然在被频繁new——每次new DamageData()都会分配新的引用对象,这才是持续内存分配的主要来源。另外,析构函数~DamageData()会让实例进入GC终结队列,反而加重了GC的负担,而且析构函数的执行时机不可控,可能导致Internal实例无法及时回池。

解决方案

1. 池化DamageData主类实例

把DamageData也纳入对象池管理,彻底避免频繁创建主类对象的内存分配。

2. 移除析构函数,改用显式释放模式

析构函数会增加GC的额外工作,改为让用户在使用完DamageData后手动调用Release方法,确保资源及时回池。

3. 优化数组复用逻辑

让DamageDataInternal和数组绑定复用,不要每次Get/Release都Rent/Return数组——因为DamageTypes的长度是固定的,数组可以跟着Internal实例一起池化,只在初始化时创建一次,后续复用只需清零即可,减少ArrayPool的操作开销。

4. 统一初始化逻辑

把DamageData的初始化逻辑移到池的actionOnGet中,避免构造函数和池操作重复初始化,提升效率。

修改后的代码示例

DamageData类(改为池化模式)

[Serializable]
public class DamageData
{
    [Serializable]
    public class DamageDataInternal
    {
        public float[] values;
        public float totalDamage;
        public int cachedHashCode;
        public bool isHashCodeDirty;
    }

    private DamageDataInternal _internalData;
    public static readonly DamageType[] DamageTypes;

    public int Hash => GetHashCode();
    public bool IsInit => _internalData != null;

    static DamageData()
    {
        DamageTypes = (DamageType[])Enum.GetValues(typeof(DamageType));
    }

    // 私有构造函数,禁止外部直接new
    private DamageData() {}

    // 显式释放方法
    public void Release()
    {
        if (_internalData == null) return;
        DamageDataInternalPool.Shared.Release(_internalData);
        _internalData = null;
        // 把当前DamageData实例回池
        DamageDataPool.Shared.Release(this);
    }

    // 从池获取实例的静态方法
    public static DamageData Get()
    {
        var data = DamageDataPool.Shared.Get();
        // 确保内部数据已初始化
        if (data._internalData == null)
        {
            data._internalData = DamageDataInternalPool.Shared.Get();
        }
        return data;
    }
}

DamageData主类池

using UnityEngine.Pool;
public class DamageDataPool
{
    private readonly ObjectPool<DamageData> _pool;
    public static DamageDataPool Shared { get; } = new();

    private DamageDataPool()
    {
        _pool = new ObjectPool<DamageData>(
            createFunc: () => new DamageData(),
            actionOnGet: obj =>
            {
                // 重置主类状态(如果需要)
            },
            actionOnRelease: obj =>
            {
                // 这里不需要额外操作,因为Release方法已经处理了Internal数据
            },
            actionOnDestroy: null,
            defaultCapacity: 10,
            maxSize: 10000
        );
    }

    public DamageData Get() => _pool.Get();
    public void Release(DamageData obj) => _pool.Release(obj);
}

修改后的DamageDataInternalPool

using UnityEngine.Pool;
public class DamageDataInternalPool
{
    private readonly ObjectPool<DamageData.DamageDataInternal> _pool;
    public static DamageDataInternalPool Shared { get; } = new();

    private DamageDataInternalPool()
    {
        _pool = new ObjectPool<DamageData.DamageDataInternal>(
            createFunc: () => new DamageData.DamageDataInternal
            {
                // 初始化时一次性创建数组,后续复用
                values = new float[DamageData.DamageTypes.Length]
            },
            actionOnGet: obj =>
            {
                obj.totalDamage = 0f;
                obj.cachedHashCode = 0;
                obj.isHashCodeDirty = true;
                // 用Array.Clear高效清零数组,比循环赋值快
                Array.Clear(obj.values, 0, obj.values.Length);
            },
            actionOnRelease: obj =>
            {
                // 不再释放数组,因为数组和Internal实例绑定复用
            },
            actionOnDestroy: obj =>
            {
                // 销毁时再释放数组(当池达到maxSize时)
                obj.values = null;
            },
            defaultCapacity: 10,
            maxSize: 10000
        );
    }

    public DamageData.DamageDataInternal Get() => _pool.Get();
    public void Release(DamageData.DamageDataInternal obj) => _pool.Release(obj);
}
使用方式
// 获取实例
var damageData = DamageData.Get();

// 使用damageData...

// 使用完毕后手动释放
damageData.Release();
额外优化建议
  • 如果DamageTypes的长度是固定的编译期常量,可以直接写死数值,避免Enum.GetValues的运行时开销。
  • 可以考虑在DamageData中实现IDisposable接口,结合using语句自动释放,但要注意生命周期未定义的场景下,手动释放更灵活。
  • 监控对象池的maxSize设置,避免池过大占用过多内存,或者过小导致频繁创建新实例。

内容的提问来源于stack exchange,提问作者waterb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:37:04