summaryrefslogtreecommitdiff
path: root/Impostor-dev/src/Impostor.Benchmarks/Data
diff options
context:
space:
mode:
Diffstat (limited to 'Impostor-dev/src/Impostor.Benchmarks/Data')
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs172
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs220
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs90
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/MessageWriter.cs311
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs26
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs22
-rw-r--r--Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs50
7 files changed, 891 insertions, 0 deletions
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs
new file mode 100644
index 0000000..2aba724
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs
@@ -0,0 +1,172 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Impostor.Benchmarks.Data
+{
+ public class MessageReader_Bytes
+ {
+ public byte Tag { get; }
+ public byte[] Buffer { get; }
+ public int Position { get; set; }
+ public int Length { get; set; }
+ public int BytesRemaining => this.Length - this.Position;
+
+ public MessageReader_Bytes(byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = byte.MaxValue;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public MessageReader_Bytes(byte tag, byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = tag;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public MessageReader_Bytes ReadMessage()
+ {
+ var length = ReadUInt16();
+ var tag = FastByte();
+ var pos = Position;
+
+ Position += length;
+ return new MessageReader_Bytes(tag, Buffer, pos, length);
+ }
+
+ public bool ReadBoolean()
+ {
+ byte val = FastByte();
+ return val != 0;
+ }
+
+ public sbyte ReadSByte()
+ {
+ return (sbyte)FastByte();
+ }
+
+ public byte ReadByte()
+ {
+ return FastByte();
+ }
+
+ public ushort ReadUInt16()
+ {
+ return (ushort)(this.FastByte() |
+ this.FastByte() << 8);
+ }
+
+ public short ReadInt16()
+ {
+ return (short)(this.FastByte() |
+ this.FastByte() << 8);
+ }
+
+ public uint ReadUInt32()
+ {
+ return this.FastByte()
+ | (uint)this.FastByte() << 8
+ | (uint)this.FastByte() << 16
+ | (uint)this.FastByte() << 24;
+ }
+
+ public int ReadInt32()
+ {
+ return this.FastByte()
+ | this.FastByte() << 8
+ | this.FastByte() << 16
+ | this.FastByte() << 24;
+ }
+
+ public unsafe float ReadSingle()
+ {
+ float output = 0;
+ fixed (byte* bufPtr = &this.Buffer[Position])
+ {
+ byte* outPtr = (byte*)&output;
+
+ *outPtr = *bufPtr;
+ *(outPtr + 1) = *(bufPtr + 1);
+ *(outPtr + 2) = *(bufPtr + 2);
+ *(outPtr + 3) = *(bufPtr + 3);
+ }
+
+ this.Position += 4;
+ return output;
+ }
+
+ public string ReadString()
+ {
+ var len = this.ReadPackedInt32();
+
+ if (this.BytesRemaining < len)
+ {
+ throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}");
+ }
+
+ var output = Encoding.UTF8.GetString(this.Buffer, Position, len);
+ this.Position += len;
+ return output;
+ }
+
+ public Span<byte> ReadBytesAndSize()
+ {
+ var len = ReadPackedInt32();
+ return ReadBytes(len);
+ }
+
+ public Span<byte> ReadBytes(int length)
+ {
+ var output = Buffer.AsSpan(Position, length);
+ Position += length;
+ return output;
+ }
+
+ public int ReadPackedInt32()
+ {
+ return (int)ReadPackedUInt32();
+ }
+
+ public uint ReadPackedUInt32()
+ {
+ bool readMore = true;
+ int shift = 0;
+ uint output = 0;
+
+ while (readMore)
+ {
+ byte b = FastByte();
+ if (b >= 0x80)
+ {
+ readMore = true;
+ b ^= 0x80;
+ }
+ else
+ {
+ readMore = false;
+ }
+
+ output |= (uint)(b << shift);
+ shift += 7;
+ }
+
+ return output;
+ }
+
+ public MessageReader_Bytes Slice(int start, int length)
+ {
+ return new MessageReader_Bytes(Tag, Buffer, start, length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private byte FastByte()
+ {
+ return Buffer[Position++];
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs
new file mode 100644
index 0000000..1103336
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Impostor.Benchmarks.Data
+{
+ public class MessageReader_Bytes_Pooled
+ {
+ private static ConcurrentQueue<MessageReader_Bytes_Pooled> _readers;
+
+ static MessageReader_Bytes_Pooled()
+ {
+ var instances = new List<MessageReader_Bytes_Pooled>();
+
+ for (var i = 0; i < 10000; i++)
+ {
+ instances.Add(new MessageReader_Bytes_Pooled());
+ }
+
+ _readers = new ConcurrentQueue<MessageReader_Bytes_Pooled>(instances);
+ }
+
+ public byte Tag { get; set; }
+ public byte[] Buffer { get; set; }
+ public int Position { get; set; }
+ public int Length { get; set; }
+ public int BytesRemaining => this.Length - this.Position;
+
+ public void Update(byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = byte.MaxValue;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public void Update(byte tag, byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = tag;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public MessageReader_Bytes_Pooled ReadMessage()
+ {
+ var length = ReadUInt16();
+ var tag = FastByte();
+ var pos = Position;
+
+ Position += length;
+
+ if (!_readers.TryDequeue(out var result))
+ {
+ throw new Exception("Failed to get pooled instance");
+ }
+
+ result.Update(tag, Buffer, pos, length);
+
+ return result;
+ }
+
+ public bool ReadBoolean()
+ {
+ byte val = FastByte();
+ return val != 0;
+ }
+
+ public sbyte ReadSByte()
+ {
+ return (sbyte)FastByte();
+ }
+
+ public byte ReadByte()
+ {
+ return FastByte();
+ }
+
+ public ushort ReadUInt16()
+ {
+ return (ushort)(this.FastByte() |
+ this.FastByte() << 8);
+ }
+
+ public short ReadInt16()
+ {
+ return (short)(this.FastByte() |
+ this.FastByte() << 8);
+ }
+
+ public uint ReadUInt32()
+ {
+ return this.FastByte()
+ | (uint)this.FastByte() << 8
+ | (uint)this.FastByte() << 16
+ | (uint)this.FastByte() << 24;
+ }
+
+ public int ReadInt32()
+ {
+ return this.FastByte()
+ | this.FastByte() << 8
+ | this.FastByte() << 16
+ | this.FastByte() << 24;
+ }
+
+ public unsafe float ReadSingle()
+ {
+ float output = 0;
+ fixed (byte* bufPtr = &this.Buffer[Position])
+ {
+ byte* outPtr = (byte*)&output;
+
+ *outPtr = *bufPtr;
+ *(outPtr + 1) = *(bufPtr + 1);
+ *(outPtr + 2) = *(bufPtr + 2);
+ *(outPtr + 3) = *(bufPtr + 3);
+ }
+
+ this.Position += 4;
+ return output;
+ }
+
+ public string ReadString()
+ {
+ var len = this.ReadPackedInt32();
+
+ if (this.BytesRemaining < len)
+ {
+ throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}");
+ }
+
+ var output = Encoding.UTF8.GetString(this.Buffer, Position, len);
+ this.Position += len;
+ return output;
+ }
+
+ public Span<byte> ReadBytesAndSize()
+ {
+ var len = ReadPackedInt32();
+ return ReadBytes(len);
+ }
+
+ public Span<byte> ReadBytes(int length)
+ {
+ var output = Buffer.AsSpan(Position, length);
+ Position += length;
+ return output;
+ }
+
+ public int ReadPackedInt32()
+ {
+ return (int)ReadPackedUInt32();
+ }
+
+ public uint ReadPackedUInt32()
+ {
+ bool readMore = true;
+ int shift = 0;
+ uint output = 0;
+
+ while (readMore)
+ {
+ byte b = FastByte();
+ if (b >= 0x80)
+ {
+ readMore = true;
+ b ^= 0x80;
+ }
+ else
+ {
+ readMore = false;
+ }
+
+ output |= (uint)(b << shift);
+ shift += 7;
+ }
+
+ return output;
+ }
+
+ public MessageReader_Bytes_Pooled Slice(int start, int length)
+ {
+ if (!_readers.TryDequeue(out var result))
+ {
+ throw new Exception("Failed to get pooled instance");
+ }
+
+ result.Update(Tag, Buffer, start, length);
+
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private byte FastByte()
+ {
+ return Buffer[Position++];
+ }
+
+ public static MessageReader_Bytes_Pooled Get(byte[] data)
+ {
+ if (!_readers.TryDequeue(out var result))
+ {
+ throw new Exception("Failed to get pooled instance");
+ }
+
+ result.Update(data);
+
+ return result;
+ }
+
+ public static void Return(MessageReader_Bytes_Pooled instance)
+ {
+ _readers.Enqueue(instance);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs
new file mode 100644
index 0000000..0066847
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Buffers.Binary;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Benchmarks.Data
+{
+ public class MessageReader_Bytes_Pooled_Improved : IDisposable
+ {
+ private readonly ObjectPool<MessageReader_Bytes_Pooled_Improved> _pool;
+
+ public MessageReader_Bytes_Pooled_Improved(ObjectPool<MessageReader_Bytes_Pooled_Improved> pool)
+ {
+ _pool = pool;
+ }
+
+ public byte Tag { get; set; }
+ public byte[] Buffer { get; set; }
+ public int Position { get; set; }
+ public int Length { get; set; }
+ public int BytesRemaining => this.Length - this.Position;
+
+ public void Update(byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = byte.MaxValue;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public void Update(byte tag, byte[] buffer, int position = 0, int length = 0)
+ {
+ Tag = tag;
+ Buffer = buffer;
+ Position = position;
+ Length = length;
+ }
+
+ public MessageReader_Bytes_Pooled_Improved ReadMessage()
+ {
+ var length = ReadUInt16();
+ var tag = ReadByte();
+ var pos = Position;
+
+ Position += length;
+
+ var result = _pool.Get();
+ result.Update(tag, Buffer, pos, length);
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public byte ReadByte()
+ {
+ return Buffer[Position++];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ushort ReadUInt16()
+ {
+ var res = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.AsSpan(Position));
+ Position += sizeof(ushort);
+ return res;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int ReadInt32()
+ {
+ var res = BinaryPrimitives.ReadInt32LittleEndian(Buffer.AsSpan(Position));
+ Position += sizeof(int);
+ return res;
+ }
+
+ public MessageReader_Bytes_Pooled_Improved Slice(int start, int length)
+ {
+ var result = _pool.Get();
+ result.Update(Tag, Buffer, start, length);
+ return result;
+ }
+
+ public void Dispose()
+ {
+ _pool.Return(this);
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/MessageWriter.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageWriter.cs
new file mode 100644
index 0000000..94ff441
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/MessageWriter.cs
@@ -0,0 +1,311 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Benchmarks.Data
+{
+ public class MessageWriter
+ {
+ private static int BufferSize = 64000;
+
+ public MessageType SendOption { get; private set; }
+
+ private Stack<int> messageStarts = new Stack<int>();
+
+ public MessageWriter(byte[] buffer)
+ {
+ this.Buffer = buffer;
+ this.Length = this.Buffer.Length;
+ }
+
+ public MessageWriter(int bufferSize)
+ {
+ this.Buffer = new byte[bufferSize];
+ }
+
+ public byte[] Buffer { get; }
+ public int Length { get; set; }
+ public int Position { get; set; }
+
+ public byte[] ToByteArray(bool includeHeader)
+ {
+ if (includeHeader)
+ {
+ byte[] output = new byte[this.Length];
+ System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length);
+ return output;
+ }
+ else
+ {
+ switch (this.SendOption)
+ {
+ case MessageType.Reliable:
+ {
+ byte[] output = new byte[this.Length - 3];
+ System.Buffer.BlockCopy(this.Buffer, 3, output, 0, this.Length - 3);
+ return output;
+ }
+ case MessageType.Unreliable:
+ {
+ byte[] output = new byte[this.Length - 1];
+ System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1);
+ return output;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ throw new NotImplementedException();
+ }
+
+ public bool HasBytes(int expected)
+ {
+ if (this.SendOption == MessageType.Unreliable)
+ {
+ return this.Length > 1 + expected;
+ }
+
+ return this.Length > 3 + expected;
+ }
+
+ public void Write(GameCode value)
+ {
+ this.Write(value.Value);
+ }
+
+ ///
+ public void StartMessage(byte typeFlag)
+ {
+ messageStarts.Push(this.Position);
+ this.Position += 2; // Skip for size
+ this.Write(typeFlag);
+ }
+
+ ///
+ public void EndMessage()
+ {
+ var lastMessageStart = messageStarts.Pop();
+ ushort length = (ushort)(this.Position - lastMessageStart - 3); // Minus length and type byte
+ this.Buffer[lastMessageStart] = (byte)length;
+ this.Buffer[lastMessageStart + 1] = (byte)(length >> 8);
+ }
+
+ ///
+ public void CancelMessage()
+ {
+ this.Position = this.messageStarts.Pop();
+ this.Length = this.Position;
+ }
+
+ public void Clear(MessageType sendOption)
+ {
+ this.messageStarts.Clear();
+ this.SendOption = sendOption;
+ this.Buffer[0] = (byte)sendOption;
+ switch (sendOption)
+ {
+ default:
+ case MessageType.Unreliable:
+ this.Length = this.Position = 1;
+ break;
+
+ case MessageType.Reliable:
+ this.Length = this.Position = 3;
+ break;
+ }
+ }
+
+ #region WriteMethods
+
+ public void Write(bool value)
+ {
+ this.Buffer[this.Position++] = (byte)(value ? 1 : 0);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(sbyte value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte value)
+ {
+ this.Buffer[this.Position++] = value;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(short value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(ushort value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(uint value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ this.Buffer[this.Position++] = (byte)(value >> 16);
+ this.Buffer[this.Position++] = (byte)(value >> 24);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(int value)
+ {
+ this.Buffer[this.Position++] = (byte)value;
+ this.Buffer[this.Position++] = (byte)(value >> 8);
+ this.Buffer[this.Position++] = (byte)(value >> 16);
+ this.Buffer[this.Position++] = (byte)(value >> 24);
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public unsafe void Write(float value)
+ {
+ fixed (byte* ptr = &this.Buffer[this.Position])
+ {
+ byte* valuePtr = (byte*)&value;
+
+ *ptr = *valuePtr;
+ *(ptr + 1) = *(valuePtr + 1);
+ *(ptr + 2) = *(valuePtr + 2);
+ *(ptr + 3) = *(valuePtr + 3);
+ }
+
+ this.Position += 4;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(string value)
+ {
+ var bytes = UTF8Encoding.UTF8.GetBytes(value);
+ this.WritePacked(bytes.Length);
+ this.Write(bytes);
+ }
+
+ public void Write(IPAddress value)
+ {
+ this.Write(value.GetAddressBytes());
+ }
+
+ public void WriteBytesAndSize(byte[] bytes)
+ {
+ this.WritePacked((uint)bytes.Length);
+ this.Write(bytes);
+ }
+
+ public void WriteBytesAndSize(byte[] bytes, int length)
+ {
+ this.WritePacked((uint)length);
+ this.Write(bytes, length);
+ }
+
+ public void WriteBytesAndSize(byte[] bytes, int offset, int length)
+ {
+ this.WritePacked((uint)length);
+ this.Write(bytes, offset, length);
+ }
+
+ public void Write(ReadOnlyMemory<byte> data)
+ {
+ Write(data.Span);
+ }
+
+ public void Write(ReadOnlySpan<byte> bytes)
+ {
+ bytes.CopyTo(this.Buffer.AsSpan(this.Position, bytes.Length));
+
+ this.Position += bytes.Length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte[] bytes)
+ {
+ Array.Copy(bytes, 0, this.Buffer, this.Position, bytes.Length);
+ this.Position += bytes.Length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte[] bytes, int offset, int length)
+ {
+ Array.Copy(bytes, offset, this.Buffer, this.Position, length);
+ this.Position += length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ public void Write(byte[] bytes, int length)
+ {
+ Array.Copy(bytes, 0, this.Buffer, this.Position, length);
+ this.Position += length;
+ if (this.Position > this.Length) this.Length = this.Position;
+ }
+
+ ///
+ public void WritePacked(int value)
+ {
+ this.WritePacked((uint)value);
+ }
+
+ ///
+ public void WritePacked(uint value)
+ {
+ do
+ {
+ byte b = (byte)(value & 0xFF);
+ if (value >= 0x80)
+ {
+ b |= 0x80;
+ }
+
+ this.Write(b);
+ value >>= 7;
+ } while (value > 0);
+ }
+
+ #endregion WriteMethods
+
+ public void Write(MessageWriter msg, bool includeHeader)
+ {
+ int offset = 0;
+ if (!includeHeader)
+ {
+ switch (msg.SendOption)
+ {
+ case MessageType.Unreliable:
+ offset = 1;
+ break;
+
+ case MessageType.Reliable:
+ offset = 3;
+ break;
+ }
+ }
+
+ this.Write(msg.Buffer, offset, msg.Length - offset);
+ }
+
+ public unsafe static bool IsLittleEndian()
+ {
+ byte b;
+ unsafe
+ {
+ int i = 1;
+ byte* bp = (byte*)&i;
+ b = *bp;
+ }
+
+ return b == 1;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs
new file mode 100644
index 0000000..802fcaa
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs
@@ -0,0 +1,26 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Benchmarks.Data.Pool
+{
+ public class MessageReader_Bytes_Pooled_ImprovedPolicy : IPooledObjectPolicy<MessageReader_Bytes_Pooled_Improved>
+ {
+ private readonly IServiceProvider _serviceProvider;
+
+ public MessageReader_Bytes_Pooled_ImprovedPolicy(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public MessageReader_Bytes_Pooled_Improved Create()
+ {
+ return new MessageReader_Bytes_Pooled_Improved(_serviceProvider.GetRequiredService<ObjectPool<MessageReader_Bytes_Pooled_Improved>>());
+ }
+
+ public bool Return(MessageReader_Bytes_Pooled_Improved obj)
+ {
+ return true;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs
new file mode 100644
index 0000000..402fbaf
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Impostor.Benchmarks.Data.Span
+{
+ public class MessageReaderOwner
+ {
+ private readonly Memory<byte> _data;
+
+ public MessageReaderOwner(Memory<byte> data)
+ {
+ _data = data;
+ }
+
+ public int Position { get; internal set; }
+ public int Length => _data.Length;
+
+ public MessageReader_Span CreateReader()
+ {
+ return new MessageReader_Span(this, byte.MaxValue, _data.Span.Slice(Position));
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs b/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs
new file mode 100644
index 0000000..5636dbe
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Buffers.Binary;
+using Impostor.Hazel;
+
+namespace Impostor.Benchmarks.Data.Span
+{
+ public ref struct MessageReader_Span
+ {
+ private readonly MessageReaderOwner _owner;
+ private readonly byte _tag;
+ private readonly Span<byte> _data;
+
+ public MessageReader_Span(MessageReaderOwner owner, byte tag, Span<byte> data)
+ {
+ _owner = owner;
+ _tag = tag;
+ _data = data;
+ }
+
+ public MessageReader_Span ReadMessage()
+ {
+ var length = ReadUInt16();
+ var tag = ReadByte();
+ var pos = _owner.Position;
+
+ _owner.Position += length;
+
+ return new MessageReader_Span(_owner, tag, _data.Slice(3, length));
+ }
+
+ public byte ReadByte()
+ {
+ return _data[_owner.Position++];
+ }
+
+ public ushort ReadUInt16()
+ {
+ var output = BinaryPrimitives.ReadUInt16LittleEndian(_data.Slice(_owner.Position));
+ _owner.Position += sizeof(ushort);
+ return output;
+ }
+
+ public int ReadInt32()
+ {
+ var output = BinaryPrimitives.ReadInt32LittleEndian(_data.Slice(_owner.Position));
+ _owner.Position += sizeof(int);
+ return output;
+ }
+ }
+}