【Unity优化】为C#定制联合(Union)提高序列化速度

发表于2018-10-10
评论0 1.09w浏览
很多时候,我们可以通过Protobuf之类的插件来执行序列化处理,当然也可以自己尝试自己手动处理序列化,只要针对数据对象和字节数组之间可以互相转化就可以了。唯一要考虑到问题就是在自己手写序列化的过程中,怎样转换你的数据,比如最基本的整形、浮点型这些。下面就来看看使用联合(Union)提高序列化速度的方法。

BitConvertor

在Unity中,有BitStream,然而好像没有什么用处,它们好像与Unet密切相关,而Unet目前生死未卜。
在CSharp中默认为我们提供了一个BitConvertor方法。通过它可以进行基础数据类型和字节数组之间的转换。然而我并不推荐你用它,这里我简单做了一个测试来解释原因。以下的代码都是放在一个组件的Update方法中去执行。
    void testConvertFloatToBytes()
    {
        float f = 10;
        for (int i = 0; i < 1000; i++)
        {
            BitConverter.GetBytes(f);
        }
    }
这个方法在做从float到byte数组的转换。然而,你应该能看出来一些问题。它在不停制造出byte[],而数组都是个对象,因此它在不断产生GC Alloc。

如下图:

因此,当在需要连续不断进行序列化的时刻(网络游戏中),这个方法是不可以使用的。

使用联合(Union)

在CSharp中默认是没有联合(Union)的,然而我们可以构建出一个类似联合的结构体。

使用结构体布局(StructLayout)属性以及字段偏移(FieldOffset)属性就可以自己定制数据在结构体中的分布,从而定制出一个联合。
[StructLayout(LayoutKind.Explicit, Size = 4)]
struct UNum
{
    [FieldOffset(0)]
    public byte b0;
    [FieldOffset(1)]
    public byte b1;
    [FieldOffset(2)]
    public byte b2;
    [FieldOffset(3)]
    public byte b3;
    [FieldOffset(0)]
    public int i;
    [FieldOffset(0)]
    public float f;
}

此时,我们进行数据转换时,可以使用以下的方式进行转换:
    protected byte[] m_datas;
    protected int m_dReader;
    void testConvertFloatToBytes_U()
    {
        float f = 10;
        for (int i = 0; i < 1000; i++)
        {
            UNum u = new UNum();
            u.f = f;
            m_datas[m_dReader++] = u.b0;
            m_datas[m_dReader++] = u.b1;
            m_datas[m_dReader++] = u.b2;
            m_datas[m_dReader++] = u.b3;
        }
    }
这个方法不会产生任何的GC,因为它没有将结果放在小数组中,而是直接放入了一个整体缓冲区。
注意m_datas需要是足够大的缓冲区,m_dReader是缓冲区读取索引。

总结

当执行连续大量的序列化时,使用联合可以很好地避免GCAlloc产生,而且效率要比BitConvertor来的高。来自:https://blog.csdn.net/AndrewFan/article/details/62227628

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引