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

Unity中C#大规模游戏经济模拟内存耗尽问题优化咨询

Unity大规模游戏经济模拟内存优化方案

针对数十亿次模拟数据存储导致的OutOfMemoryException问题,结合需保留完整历史记录的需求,以下是具体优化方案:

1. 用分段数组替代List减少冗余开销

List<double>会在容量不足时自动翻倍扩容,导致大量冗余内存占用(比如存储10亿条数据时,List内部数组可能预留远超实际需要的空间)。改用分段数组结构(多个固定大小的数组),可避免单个数组的过度扩容,同时降低内存碎片化。

修改后的Stat类示例:

public class Stat
{
    public string Name { get; private set; }
    private const int SegmentSize = 1000000; // 每个分段存储100万条数据
    private List<double[]> _segments;
    private int _currentSegmentIndex;
    private int _currentSegmentCount;

    public Stat(string _name = "")
    {
        Name = _name;
        _segments = new List<double[]>();
        AddNewSegment();
    }

    private void AddNewSegment()
    {
        _segments.Add(new double[SegmentSize]);
        _currentSegmentIndex = _segments.Count - 1;
        _currentSegmentCount = 0;
    }

    public void AddToSampleSpace(double _sample)
    {
        if (_currentSegmentCount >= SegmentSize)
        {
            AddNewSegment();
        }
        _segments[_currentSegmentIndex][_currentSegmentCount] = _sample;
        _currentSegmentCount++;
    }

    // 遍历所有数据的方法
    public IEnumerable<double> GetAllSamples()
    {
        foreach (var segment in _segments)
        {
            int count = segment == _segments[_currentSegmentIndex] ? _currentSegmentCount : SegmentSize;
            for (int i = 0; i < count; i++)
            {
                yield return segment[i];
            }
        }
    }
}

2. 选用更紧凑的数据类型降低单条内存占用

如果经济模拟的数值精度允许(比如投注金额仅需保留到分,或模拟误差在可接受范围内),用float替代double,单条数据内存占用从8字节降到4字节,直接减少50%内存消耗。若需高精度,可将金额转换为整数单位(比如以“分”为单位存储long类型),同样占用8字节,还能避免浮点数精度损耗。

示例(用float存储):

// 将分段数组改为float[]
private List<float[]> _segments;

public void AddToSampleSpace(float _sample)
{
    // 逻辑同分段数组方案,替换类型即可
}

3. 内存映射文件实现磁盘级存储

当数据量达十亿级别时,内存无法容纳全部数据,可借助内存映射文件将数据直接存储到磁盘,通过虚拟内存机制访问,进程仅占用少量内存即可操作超大规模数据集。

修改后的Stat类示例:

using System.IO.MemoryMappedFiles;

public class Stat : IDisposable
{
    public string Name { get; private set; }
    private MemoryMappedFile _mmf;
    private MemoryMappedViewAccessor _accessor;
    private long _currentPosition;
    private const long InitialCapacity = 1000000000; // 初始预留10亿条数据的空间

    public Stat(string _name = "")
    {
        Name = _name;
        // 创建或打开内存映射文件,存储到本地磁盘
        _mmf = MemoryMappedFile.CreateFromFile(
            $"{_name}_stats.dat", 
            System.IO.FileMode.Create, 
            $"{_name}_stats", 
            InitialCapacity * sizeof(double)
        );
        _accessor = _mmf.CreateViewAccessor();
        _currentPosition = 0;
    }

    public void AddToSampleSpace(double _sample)
    {
        _accessor.Write(_currentPosition * sizeof(double), _sample);
        _currentPosition++;
    }

    // 读取指定位置的数据
    public double GetSample(long index)
    {
        if (index >= _currentPosition)
            throw new IndexOutOfRangeException();
        return _accessor.ReadDouble(index * sizeof(double));
    }

    public void Dispose()
    {
        _accessor?.Dispose();
        _mmf?.Dispose();
    }
}

注意:使用后需调用Dispose()释放资源,避免文件句柄泄漏。

4. 分层存储:内存缓存+磁盘持久化

如果不需要实时访问全部历史数据,可采用内存缓存最近数据+磁盘持久化历史数据的方案:仅将最近的N条数据保留在内存中,旧数据批量序列化到磁盘文件,维护索引记录数据位置,查询时再从磁盘读取。

核心逻辑:

  • 内存中保留固定大小的分段数组或Queue<double>,达到上限时将批量数据写入磁盘
  • 用索引文件记录每个磁盘文件的起始/结束数据索引,实现快速定位

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 18:39:42