UnityC#教程——通过用结构替换类来优化代码

通过用结构替换类来优化代码,Unity C# 教程

新的改变

Allocation 分配

让我们从简单的代码开始,该代码演示了创建类和结构之间的性能比较。
我们将创建 100 万个:

```csharp
public sealed class Example: MonoBehaviour
{
    private ClassData[] _classArray;
    private StructData[] _structArray;

    private void Start()
    {
        Profiler.BeginSample("Class creation");    //for performance measurements
        _classArray = new ClassData[1000000];
        for (int i = 0; i < _classArray.Length; i++)
            _classArray[i] = new ClassData();
        Profiler.EndSample();  //for performance measurements

        Profiler.BeginSample("Struct creation");    //for performance measurements
        _structArray = new StructData[1000000];
        for (int i = 0; i < _structArray.Length; i++)
            _structArray[i] = new StructData();
        Profiler.EndSample();  //for performance measurements
    }

    private sealed class ClassData
    {
        public int Value { get; }
    }

    private readonly struct StructData
    {
        public int Value { get; }
    }
}

```

Unity 编辑器分析器屏幕截图

让我们比较一下:

Class 类
  • 26.7mb allocation - 26.7MB 分配
  • 40.58ms - 40.58毫秒
Struct 结构
  • 3.8mb allocation (~7x less)- 3.8MB 分配(~7 倍少)
  • 1.88ms (~21x faster) - 1.88ms(~21 倍快)

如何改变文本的样式

这些数字对于不同类型的数据会有所不同,但我们可以清楚地看到结构在这里表现得更好。

为什么?

类的每个新实例都需要在托管内存中进行分配,这会产生一些性能成本。
此外,任何类实例都需要比结构更多的内存,并且必须在托管内存内对齐。
此外,请务必记住,每个类实例都是在堆中单独分配的,并参与垃圾回收循环。这种对性能的影响不会在分配时结束,它会影响以后的 GC 收集。这实际上是考虑结构的更重要原因。

用法

迭 代

让我们添加遍历所有已创建对象的代码

private void Update()
{
    Profiler.BeginSample("Iterate Classes");    //for performance measurements
    IterateClasses();
    Profiler.EndSample();  //for performance measurements

    Profiler.BeginSample("Iterate Structs");    //for performance measurements
    IterateStructs();
    Profiler.EndSample();  //for performance measurements
}

private void IterateClasses()
{
    int sum = 0;
    for (int i = 0; i < _classArray.Length; i++)
        sum += _classArray[i].Value;
}

private void IterateStructs()
{
    int sum = 0;
    for (int i = 0; i < _structArray.Length; i++)
        sum += _structArray[i].Value;
}

在这里插入图片描述

Classes 类

  • 6.58ms - 6.58毫秒

Structs 结构

  • 4.28ms (~1.5x faster) - 4.28ms(~1.5 倍快)

数组内元素的“数据局部性”。
如果它是结构的数组(或集合),那么内存中的数据直接位于数组内部并按顺序进行,这会增加“缓存命中”。
与此相反,类的数组(或集合)仅包含对实例的引用,并且这些实例可能随机分散在内存中,因此性能会因情况而异。
此外,还有访问类实例的基础取消引用。

通过

有 3 种方法可以将这些值传递给方法:

  • Class 类
  • Struct 结构
  • 引用结构(ref/in/out 关键字)
    我们将尝试所有这些方法并测量:
private int _sum;

private void Update()
{
    ClassData classData = new();
    StructData structData = new();

    Profiler.BeginSample("Pass class");
    for (int i = 0; i < 1000000; i++)
        PassClass(classData);
    Profiler.EndSample();

    Profiler.BeginSample("Pass struct");
    for (int i = 0; i < 1000000; i++)
        PassStruct(structData);
    Profiler.EndSample();

    Profiler.BeginSample("Pass struct by ref");
    for (int i = 0; i < 1000000; i++)
        PassStructByRef(in structData);
    Profiler.EndSample();
}

private void PassClass(ClassData data) => _sum += data.Value;
private void PassStruct(StructData data) => _sum += data.Value;
private void PassStructByRef(in StructData data) => _sum += data.Value;

在这里插入图片描述
在这种情况下,通过引用传递结构是最快的。
现在让我们修改数据以占用更多内存:

private sealed class ClassData
{
    public int Value { get; }
    public int Value1 { get; }
    public int Value2 { get; }
    public int Value3 { get; }
    public int Value4 { get; }
}

private readonly struct StructData
{
    public int Value { get; }
    public int Value1 { get; }
    public int Value2 { get; }
    public int Value3 { get; }
    public int Value4 { get; }
}

在这里插入图片描述
直接传递结构变得最繁重,因为更多的数据意味着更多的时间来复制它。
但是通过引用传递结构仍然具有最佳性能。
如果可能的话,考虑通过引用传递结构,特别是如果它在内存方面不是很小的结构。

建议

  • 最有可能的是,你不需要可变结构,除非你知道你在做什么,否则更喜欢使用只读结构
  • 考虑在代码中性能较高的部分以及包含大量不太重的数据时使用结构
  • 不要制作太重的结构。如果对象占用大量内存,则通常最好将其设置为类,否则复制会降低性能
  • 使用结构时,请避免“装箱”。如果你发现需要“装箱”一些东西,可以考虑使用类来代替。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小云同志你好

谁能书阁下,白首太玄经

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值