将字节写入结构中的字节数组的最佳方法?

Best way to write bytes to a byte array in a struct?

提问人:Chroma 提问时间:3/11/2022 最后编辑:Chroma 更新时间:3/11/2022 访问量:731

问:

我正在尝试创建一个数据包结构,它基本上是固定长度的字节构建器。我有一个以 3 种不同方式编写的 WriteByte 函数。只是想知道哪个是最好的(或者是否有更好的方法),哪个会让 GC 满意。顺便说一句,我有一个位置字段,在写入字节时必须更新。这些函数将扩展为包括 WriteUInt16、WriteFloat 等...不确定最好的方法是什么。任何建议都是值得赞赏的。

  1. 这应该是一个结构体吗?我想尽可能少地进行分配,因为这些包将非常频繁地创建。
  2. 我应该将 WriteByte 作为方法(选项 1)、作为 Pack 的扩展(由 ref 传递)(选项 2)还是只使用静态帮助程序类(由 ref 传递)(选项 3)?

代码如下:

public struct Pack
{
    public byte[] Data { get; internal set; }
    public int Pos { get; internal set; }
    
    public Pack(byte opcode, ushort size)
    {
        ++size;                     // make room for byte opcode
        Data = new byte[2 + size];  // make room to prepend the size (ushort)
        BitConverter.GetBytes(size).CopyTo(Data, 0);
        Data[2] = opcode;
        Pos = 3;                    // start writing at position 3
    }

    // option 1
    // use: pack.WriteByte(0x01)
    public void WriteByte(byte value) => Data[Pos++] = value;
}

public static class SPackExtensions
{
    // option 2
    // use: pack.WriteByte(0x01)
    public static void WriteByte(ref this Pack pack, byte value)
    {
        pack.Data[pack.Pos++] = value;
    }
}

public static class PackWriter
{
    // option 3
    // use: PackWriter.WriteByte(ref pack, 0x01)
    public static void WriteByte(ref Pack pack, byte value)
    {
        pack.Data[pack.Pos++] = value;
    }
}
C# 结构 数据包

评论

0赞 canton7 3/11/2022
它们之间没有任何区别。从字面上看,零。将它们粘贴到 sharplab.io 中,然后查看“JIT ASM”以获取“发布版本”(生成的 URL 太长,无法在此处粘贴)
0赞 Jeroen Mostert 3/11/2022
如果你真的喜欢将字节放入结构中并保持 GC 满意,请考虑 、s、甚至(取决于你的方案)System.IO.Pipelines。基于堆的数组,效率明显较低。ref structSpanBinaryPrimitivesBitConverter
0赞 Sweeper 3/11/2022
赛跑你的马
1赞 canton7 3/11/2022
但是,一定要使用而不是在那里:这将为您节省数组分配,并且您可以将数据直接写入后备数组。您还可以明确字节序BinaryPrimitivesBitConverter

答:

0赞 JonasH 3/11/2022 #1

这应该是一个结构体吗?我想尽可能少地进行分配,因为这些包将非常频繁地创建。

或。即使使用结构,您仍将分配两个对象,一个在将大小转换为数组时,另一个用于数组本身。因此,分配对象的开销可能可以忽略不计,特别是因为它可能比数组小得多。但是,建议结构是不可变的,因为如果共享数据数组,但 Pos 不是,则在传递对象时可能会非常混乱。

但是,在编写长度时,您可能应该使用或避免分配。BitConverter.TryWriteBytesBinaryPrimitives.TryWriteInt16LittleEndian

我应该将 WriteByte 作为方法(选项 1)、作为 Pack 的扩展(由 ref 传递)(选项 2)还是只使用静态帮助程序类(由 ref 传递)(选项 3)?

生成的代码应该是相同的,因此请选择最容易使用且最易读的代码。我可能会支持选项 1。在我看来,在这种情况下,使用扩展方法或帮助程序类不会带来什么好处。

这些包将非常频繁地创建

在编写性能优化代码时,一个好的原则是完全避免分配。据我所知,这个对象不可能放入对象池中并重用,因为位置参数无法重置。因此,我会考虑将我的对象基于从固定大小缓冲区池中获取的跨度。

另外,我强烈建议您分析您的代码。“非常频繁”的范围可能从 1hz 到 10^9hz 不等,具体取决于上下文。因此,您应该检查这是否是您的特定上下文中的实际问题。