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

发布于 2025-01-13 21:43:01 字数 1382 浏览 3 评论 0原文

我正在尝试创建一个数据包结构,它基本上是一个固定长度的字节生成器。我有一个以 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;
    }
}

I'm trying to create a packet struct that is basically a byte builder of fixed length. I have a WriteByte function written in 3 different ways. Just wondering which is best (or if there's a better way altogether) and which will keep the GC happy. BTW, I have a Position field that has to be updated when a byte(s) is written. The functions will expand to include WriteUInt16, WriteFloat etc... Not sure what the best approach is. Any advice is appreciated.

  1. Should this be a struct? I'd like to do as little allocation as possible because these Packs will be created very frequently.
  2. Should I put the WriteByte in the struct itself as a method (option 1), as an extension of Pack (passed by ref) (option 2), or just use a static helper class (passed by ref) (option 3)?

Here's the code:

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;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

Saygoodbye 2025-01-20 21:43:01

这应该是一个结构吗?我希望进行尽可能少的分配,因为这些包将被频繁创建。

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

但您可能应该使用 BitConverter.TryWriteBytes 或 BinaryPrimitives.TryWriteInt16LittleEndian 来避免写入长度时的分配。

我应该将 WriteByte 放在结构本身中作为方法(选项 1),作为 Pack 的扩展(通过 ref 传递)(选项 2),还是只使用静态帮助器类(通过 ref 传递)(选项 3) )?

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

这些包将被频繁创建

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

另外,我强烈建议您分析您的代码。根据上下文,“非常频繁”的范围可能从 1hz 到 10^9hz。因此,您应该检查这在您的特定环境中是否是一个实际问题。

Should this be a struct? I'd like to do as little allocation as possible because these Packs will be created very frequently.

Maybe. Even with a struct you will still be allocating two objects, one when converting the size to an array, and one for the array itself. So the overhead for allocating the object will probably be negligible, especially since it will probably be much smaller than the array. However, structs are recommended to be immutable, since it can be quite confusing when passing around a object if the data-array is shared, but the Pos is not.

But you should probably use BitConverter.TryWriteBytes or BinaryPrimitives.TryWriteInt16LittleEndian to avoid the allocation when writing the length.

Should I put the WriteByte in the struct itself as a method (option 1), as an extension of Pack (passed by ref) (option 2), or just use a static helper class (passed by ref) (option 3)?

The generated code should be identical, so go for whatever is easiest to use and most readable. I would probably argue for option 1. In my opinion, using extension methods or helper classes would bring little advantages in this case.

these Packs will be created very frequently

When writing performance optimized code, a good principle is avoiding allocations altogether. And as far as I can see this object would be impossible to put into a object pool and reuse, since the position parameter is not possible to reset. So I would consider basing my object around a span that is fetched from a pool of fixed size buffers.

Also, I would highly recommend that you profile your code. "Very frequently" might range from a 1hz to 10^9hz depending on context. So you should check if this is an actual problem in your particular context.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文