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
相关产品推荐
相关产品推荐

