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

处理百万级实体:选择Class还是Struct更合适?

应用场景

  • 内存中处理百万级甚至更多实体
  • 在无限循环中处理(每轮最多1ms)
  • 快速加载和保存至二进制文件
  • 大量以实体为参数的链式方法调用

无法实例化所有实体,分配成本过高,计划将其加载至byte[]数组,按需使用。

Class方案

public class Entity : IDisposable
{
    public Pool Pool { get; }

    private readonly int _size;

    private int _id;

    private int _offset;

    private readonly Property[] _properties;

    private readonly byte[] _tape;

    public int Id
    {
        get => _id;
        set
        {
            _id = value;
            _offset = value * _size;
        }
    }

    public int Value1
    {
        get => GetInt32(0);
        set => SetInt32(0, value);
    }

    public int Value2
    {
        get => GetInt32(1);
        set => SetInt32(1, value);
    }

    public Entity(Pool pool)
    {
        Pool = pool;
        _size = pool.Schema.Size;
        _properties = pool.Schema.GetProperties();
        _tape = pool.Tape;
    }

    public int GetInt32(int index)
    {
        var offset = GetOffset(index);

        return _tape[offset] << 24 |
               _tape[offset + 1] << 16 |
               _tape[offset + 2] << 8 |
               _tape[offset + 3];
    }

    public void SetInt32(int index, int value)
    {
        var offset = GetOffset(index);

        _tape[offset] = (byte)(value >> 24);
        _tape[offset + 1] = (byte)(value >> 16);
        _tape[offset + 2] = (byte)(value >> 8);
        _tape[offset + 3] = (byte)value;
    }

    private int GetOffset(int index)
    {
        var property = _properties[index];

        return _offset + property.Offset;
    }

    public void Dispose()
    {
        Pool.Release(this);
    }
}

使用示例:

while(true)
{
    using(var entity = pool.Lease())
    {
        entity.Id = 123;
        entity.Value1 = entity.Value2 + 123;
        DoSomething1(entity);
        DoSomething2(entity);
    }
}

Struct方案

public struct Entity
{
    public Pool Pool { get; }

    private readonly int _size;

    private int _id;

    private int _offset;

    private readonly Property[] _properties;

    private readonly byte[] _tape;

    public int Id
    {
        get => _id;
        set
        {
            _id = value;
            _offset = value * _size;
        }
    }

    public int Value1
    {
        get => GetInt32(0);
        set => SetInt32(0, value);
    }

    public int Value2
    {
        get => GetInt32(1);
        set => SetInt32(1, value);
    }

    public Entity(Pool pool)
    {
        Pool = pool;
        _size = pool.Schema.Size;
        _properties = pool.Schema.GetProperties();
        _tape = pool.Tape;
    }

    public int GetInt32(int index)
    {
        var offset = GetOffset(index);

        return _tape[offset] << 24 |
               _tape[offset + 1] << 16 |
               _tape[offset + 2] << 8 |
               _tape[offset + 3];
    }

    public void SetInt32(int index, int value)
    {
        var offset = GetOffset(index);

        _tape[offset] = (byte)(value >> 24);
        _tape[offset + 1] = (byte)(value >> 16);
        _tape[offset + 2] = (byte)(value >> 8);
        _tape[offset + 3] = (byte)value;
    }

    private int GetOffset(int index)
    {
        var property = _properties[index];

        return _offset + property.Offset;
    }
}

使用示例:

while(true)
{
    var entity = new Entity(pool);
    entity.Id = 123;
    entity.Value1 = entity.Value2 + 123;
    DoSomething1(entity);
    DoSomething2(entity);
}

问题解答

哪种方案更优?

结合你的场景,Struct方案更适合,但要注意值类型带来的潜在陷阱;Class方案仅在需要多态、接口支持或复杂对象生命周期管理时才有优势,在你的高频率、低延迟需求下,Struct的性能表现更适配。

该场景下需要考虑的核心因素

  1. 内存分配与GC压力

    • Class是引用类型,即使依赖对象池,池为空时仍需在堆上分配新实例,频繁创建回收会触发GC停顿,直接影响每轮1ms的时间限制。
    • Struct是值类型,默认在栈上分配,创建实例几乎无分配成本,不会产生GC压力,完美契合无限循环的低延迟要求。
  2. 性能与调用开销

    • Struct的成员访问是直接栈内存操作,比Class的堆内存间接访问更快,百万级实体处理场景下,这种微小差距会被放大。
    • 注意:如果Struct作为参数传递时未用ref/in修饰,会发生值拷贝。若Struct体积较大(比如包含多个引用字段),拷贝成本会上升,建议用ref传递避免拷贝,例如DoSomething1(ref entity)。
  3. 对象池的必要性

    • Class方案依赖对象池规避分配,但池本身有管理开销(锁竞争、空闲链表维护),极高并发场景下可能成为瓶颈。
    • Struct方案无需对象池,直接创建即可,省去池的管理成本,更适合无限循环场景。
  4. 装箱风险

    • 若将Struct传递给接受object或接口类型的方法,会触发装箱操作,导致堆分配和GC。需确保所有业务方法直接接受Struct类型,或用in/ref修饰符避免装箱。
  5. 状态一致性

    • Struct是值类型,拷贝后为独立实例,方法中修改拷贝后的Struct不会影响原实例状态。需确保业务逻辑不存在依赖引用传递的场景,避免出现意外行为。
  6. 字段初始化与不可变性

    • Struct的只读字段必须在构造函数中初始化,你的方案中_size、_properties、_tape均为只读,符合从Pool获取固定值的场景需求,无需额外调整。

总结

优先选择Struct方案,同时优化方法参数传递(用ref/in避免拷贝)、规避装箱操作,就能最大化性能并消除GC压力。如果后续需要支持多态或接口,再考虑Class+对象池方案,但要做好池的性能优化(比如无锁设计)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 20:15:36