]> git.deb.at Git - rhonda/impostor.git/commitdiff
MessageReader benchmark stuff
authorAeonLucid <aeonlucid@outlook.com>
Sat, 31 Oct 2020 21:04:31 +0000 (22:04 +0100)
committerAeonLucid <aeonlucid@outlook.com>
Sat, 31 Oct 2020 21:04:31 +0000 (22:04 +0100)
13 files changed:
src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs [new file with mode: 0644]
src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs [new file with mode: 0644]
src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs [new file with mode: 0644]
src/Impostor.Benchmarks/Data/MessageWriter.cs [new file with mode: 0644]
src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs [new file with mode: 0644]
src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs [new file with mode: 0644]
src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs [new file with mode: 0644]
src/Impostor.Benchmarks/Extensions/SpanExtensions.cs [new file with mode: 0644]
src/Impostor.Benchmarks/Impostor.Benchmarks.csproj
src/Impostor.Benchmarks/Program.cs
src/Impostor.Benchmarks/Tests/EventManagerBenchmark.cs
src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs [new file with mode: 0644]
src/Impostor.Tests/Hazel/MessageReaderTests.cs

diff --git a/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs b/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs
new file mode 100644 (file)
index 0000000..2aba724
--- /dev/null
@@ -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/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs b/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs
new file mode 100644 (file)
index 0000000..1103336
--- /dev/null
@@ -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/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs b/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs
new file mode 100644 (file)
index 0000000..0066847
--- /dev/null
@@ -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/src/Impostor.Benchmarks/Data/MessageWriter.cs b/src/Impostor.Benchmarks/Data/MessageWriter.cs
new file mode 100644 (file)
index 0000000..94ff441
--- /dev/null
@@ -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/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs b/src/Impostor.Benchmarks/Data/Pool/MessageReader_Bytes_Pooled_ImprovedPolicy.cs
new file mode 100644 (file)
index 0000000..802fcaa
--- /dev/null
@@ -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/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs b/src/Impostor.Benchmarks/Data/Span/MessageReaderOwner.cs
new file mode 100644 (file)
index 0000000..402fbaf
--- /dev/null
@@ -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/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs b/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs
new file mode 100644 (file)
index 0000000..5636dbe
--- /dev/null
@@ -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;
+        }
+    }
+}
diff --git a/src/Impostor.Benchmarks/Extensions/SpanExtensions.cs b/src/Impostor.Benchmarks/Extensions/SpanExtensions.cs
new file mode 100644 (file)
index 0000000..8fbceeb
--- /dev/null
@@ -0,0 +1,24 @@
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+
+namespace Impostor.Benchmarks.Extensions
+{
+    public static class SpanExtensions
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Span<byte> ReadMessage(this Span<byte> input)
+        {
+            var length = BinaryPrimitives.ReadUInt16LittleEndian(input);
+            var tag = input[2];
+
+            return input.Slice(3, length);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static int ReadUInt16(this ref ReadOnlySpan<byte> input)
+        {
+            return BinaryPrimitives.ReadUInt16LittleEndian(input);
+        }
+    }
+}
index 697de5215bef2d51392fe6bf79a00b6da191905a..6f19bda787036b1f6ae06f3943bacd7af49071d6 100644 (file)
@@ -3,14 +3,15 @@
   <PropertyGroup>
     <OutputType>Exe</OutputType>
     <TargetFramework>net5.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="BenchmarkDotNet" Version="0.12.1"/>
+    <PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\Impostor.Server\Impostor.Server.csproj"/>
+    <ProjectReference Include="..\Impostor.Server\Impostor.Server.csproj" />
   </ItemGroup>
 
 </Project>
index 213070fa1129bf4d92e399784e25fdce7f52a30d..3ce73837afec2faf606f1a586e4f0001a8511930 100644 (file)
@@ -9,7 +9,12 @@ namespace Impostor.Benchmarks
     {
         private static void Main(string[] args)
         {
-            BenchmarkRunner.Run<EventManagerBenchmark>(
+            // BenchmarkRunner.Run<EventManagerBenchmark>(
+            //     DefaultConfig.Instance
+            //         .AddDiagnoser(MemoryDiagnoser.Default)
+            // );
+
+            BenchmarkRunner.Run<MessageReaderBenchmark>(
                 DefaultConfig.Instance
                     .AddDiagnoser(MemoryDiagnoser.Default)
             );
index 4470d92a56baf176b0e42609047a57dac449c98d..0675080b737e6208b33ef86351b61540c81ec98a 100644 (file)
@@ -1,77 +1,77 @@
-using System.Threading.Tasks;
-using BenchmarkDotNet.Attributes;
-using Impostor.Api.Events;
-using Impostor.Api.Events.Managers;
-using Impostor.Server.Events;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Impostor.Benchmarks.Tests
-{
-    public class EventManagerBenchmark
-    {
-        private IEventManager _eventManager;
-        private IGameEvent _event;
-
-        [GlobalSetup]
-        public void Setup()
-        {
-            var services = new ServiceCollection();
-
-            services.AddLogging();
-            services.AddSingleton<IEventManager, EventManager>();
-
-            _event = new GameStartedEvent(null);
-            _eventManager = services.BuildServiceProvider().GetRequiredService<IEventManager>();
-            _eventManager.RegisterListener(new EventListener());
-            _eventManager.RegisterListener(new EventListener());
-            _eventManager.RegisterListener(new EventListener());
-            _eventManager.RegisterListener(new EventListener());
-            _eventManager.RegisterListener(new EventListener());
-        }
-
-        [Benchmark]
-        public async Task Run_1()
-        {
-            for (var i = 0; i < 1; i++)
-            {
-                await _eventManager.CallAsync(_event);
-            }
-        }
-
-        [Benchmark]
-        public async Task Run_1000()
-        {
-            for (var i = 0; i < 1000; i++)
-            {
-                await _eventManager.CallAsync(_event);
-            }
-        }
-
-        [Benchmark]
-        public async Task Run_10000()
-        {
-            for (var i = 0; i < 10000; i++)
-            {
-                await _eventManager.CallAsync(_event);
-            }
-        }
-
-        [Benchmark]
-        public async Task Run_100000()
-        {
-            for (var i = 0; i < 100000; i++)
-            {
-                await _eventManager.CallAsync(_event);
-            }
-        }
-
-        private class EventListener : IEventListener
-        {
-            [EventListener]
-            public void OnGameStarted(IGameStartedEvent e)
-            {
-
-            }
-        }
-    }
-}
+// using System.Threading.Tasks;
+// using BenchmarkDotNet.Attributes;
+// using Impostor.Api.Events;
+// using Impostor.Api.Events.Managers;
+// using Impostor.Server.Events;
+// using Microsoft.Extensions.DependencyInjection;
+//
+// namespace Impostor.Benchmarks.Tests
+// {
+//     public class EventManagerBenchmark
+//     {
+//         private IEventManager _eventManager;
+//         private IGameEvent _event;
+//
+//         [GlobalSetup]
+//         public void Setup()
+//         {
+//             var services = new ServiceCollection();
+//
+//             services.AddLogging();
+//             services.AddSingleton<IEventManager, EventManager>();
+//
+//             _event = new GameStartedEvent(null);
+//             _eventManager = services.BuildServiceProvider().GetRequiredService<IEventManager>();
+//             _eventManager.RegisterListener(new EventListener());
+//             _eventManager.RegisterListener(new EventListener());
+//             _eventManager.RegisterListener(new EventListener());
+//             _eventManager.RegisterListener(new EventListener());
+//             _eventManager.RegisterListener(new EventListener());
+//         }
+//
+//         [Benchmark]
+//         public async Task Run_1()
+//         {
+//             for (var i = 0; i < 1; i++)
+//             {
+//                 await _eventManager.CallAsync(_event);
+//             }
+//         }
+//
+//         [Benchmark]
+//         public async Task Run_1000()
+//         {
+//             for (var i = 0; i < 1000; i++)
+//             {
+//                 await _eventManager.CallAsync(_event);
+//             }
+//         }
+//
+//         [Benchmark]
+//         public async Task Run_10000()
+//         {
+//             for (var i = 0; i < 10000; i++)
+//             {
+//                 await _eventManager.CallAsync(_event);
+//             }
+//         }
+//
+//         [Benchmark]
+//         public async Task Run_100000()
+//         {
+//             for (var i = 0; i < 100000; i++)
+//             {
+//                 await _eventManager.CallAsync(_event);
+//             }
+//         }
+//
+//         private class EventListener : IEventListener
+//         {
+//             [EventListener]
+//             public void OnGameStarted(IGameStartedEvent e)
+//             {
+//
+//             }
+//         }
+//     }
+// }
diff --git a/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs b/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs
new file mode 100644 (file)
index 0000000..c64ace9
--- /dev/null
@@ -0,0 +1,132 @@
+using System;
+using System.Buffers.Binary;
+using BenchmarkDotNet.Attributes;
+using Impostor.Benchmarks.Data;
+using Impostor.Benchmarks.Data.Pool;
+using Impostor.Benchmarks.Extensions;
+using Impostor.Hazel;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.ObjectPool;
+using MessageWriter = Impostor.Benchmarks.Data.MessageWriter;
+
+namespace Impostor.Benchmarks.Tests
+{
+    public class MessageReaderBenchmark
+    {
+        private byte[] _data;
+        private Microsoft.Extensions.ObjectPool.ObjectPool<MessageReader_Bytes_Pooled_Improved> _pool;
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            var message = new MessageWriter(1024);
+
+            message.StartMessage(1);
+            message.Write((ushort)3100);
+            message.Write((byte)100);
+            message.Write((int) int.MaxValue);
+            message.WritePacked(int.MaxValue);
+            message.EndMessage();
+
+            _data = message.ToByteArray(true);
+
+            MessageReader_Bytes_Pooled.Return(MessageReader_Bytes_Pooled.Get(_data));
+
+            // Services
+            var services = new ServiceCollection();
+
+            services.AddSingleton<ObjectPoolProvider>(new DefaultObjectPoolProvider());
+            services.AddSingleton(serviceProvider =>
+            {
+                var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
+                var policy = new MessageReader_Bytes_Pooled_ImprovedPolicy(serviceProvider);
+                return provider.Create(policy);
+            });
+
+            _pool = services
+                .BuildServiceProvider()
+                .GetRequiredService<Microsoft.Extensions.ObjectPool.ObjectPool<MessageReader_Bytes_Pooled_Improved>>();
+        }
+
+        [Benchmark]
+        public void Span_Run_1_000_000()
+        {
+            for (var i = 0; i < 1_000_000; i++)
+            {
+                var span = _data.AsSpan();
+                var inner = span.ReadMessage();
+
+                _ = BinaryPrimitives.ReadUInt16LittleEndian(inner);
+                _ = inner[2];
+                _ = BinaryPrimitives.ReadInt32LittleEndian(inner.Slice(3));
+            }
+        }
+
+        [Benchmark]
+        public void Normal_Run_1_000_000()
+        {
+            for (var i = 0; i < 1_000_000; i++)
+            {
+                var reader = new MessageReader(_data);
+                var inner = reader.ReadMessage();
+
+                _ = inner.ReadUInt16();
+                _ = inner.ReadByte();
+                _ = inner.ReadInt32();
+                // inner.ReadPackedInt32();
+            }
+        }
+
+        [Benchmark]
+        public void Bytes_Run_1_000_000()
+        {
+            for (var i = 0; i < 1_000_000; i++)
+            {
+                var reader = new MessageReader_Bytes(_data);
+                var inner = reader.ReadMessage();
+
+                _ = inner.ReadUInt16();
+                _ = inner.ReadByte();
+                _ = inner.ReadInt32();
+                // inner.ReadPackedInt32();
+            }
+        }
+
+        [Benchmark]
+        public void Pooled_Bytes_Run_1_000_000()
+        {
+            for (var i = 0; i < 1_000_000; i++)
+            {
+                var reader = MessageReader_Bytes_Pooled.Get(_data);
+                var inner = reader.ReadMessage();
+
+                _ = inner.ReadUInt16();
+                _ = inner.ReadByte();
+                _ = inner.ReadInt32();
+                // inner.ReadPackedInt32();
+
+                MessageReader_Bytes_Pooled.Return(inner);
+                MessageReader_Bytes_Pooled.Return(reader);
+            }
+        }
+
+        [Benchmark]
+        public void Improved_Pooled_Bytes_Run_1_000_000()
+        {
+            using (var reader = _pool.Get())
+            {
+                for (var i = 0; i < 1_000_000; i++)
+                {
+                    reader.Update(_data);
+
+                    using (var inner = reader.ReadMessage())
+                    {
+                        _ = inner.ReadUInt16();
+                        _ = inner.ReadByte();
+                        _ = inner.ReadInt32();
+                    }
+                }
+            }
+        }
+    }
+}
index a8b97963a80e7bc1f23d0e14c51c2bfc5117fe37..0d1f6135880ddd8e878f8c51454f8eac06f35cc1 100644 (file)
@@ -158,10 +158,70 @@ namespace Impostor.Tests.Hazel
             Assert.Equal("NO", sub.ReadString());
         }
 
+        [Fact]
+        public void RemoveMessage()
+        {
+            // Create expected message.
+            var messageExpected = new MessageWriter(1024);
+
+            messageExpected.StartMessage(0);
+            messageExpected.StartMessage(1);
+            messageExpected.Write("HiTest");
+            messageExpected.EndMessage();
+            messageExpected.StartMessage(2);
+            messageExpected.Write("HiTest");
+            messageExpected.EndMessage();
+            messageExpected.EndMessage();
+
+            // Create message.
+            var messageWriter = new MessageWriter(1024);
+
+            messageWriter.StartMessage(0);
+                messageWriter.StartMessage(1);
+                    messageWriter.Write("HiTest1");
+                    messageWriter.StartMessage(2);
+                        messageWriter.Write("RemoveMe!");
+                    messageWriter.EndMessage();
+                messageWriter.EndMessage();
+                messageWriter.StartMessage(2);
+                    messageWriter.Write("HiTest2");
+                messageWriter.EndMessage();
+            messageWriter.EndMessage();
+
+            // Do the magic.
+            var reader = new MessageReader(messageWriter.Buffer);
+            var inner = reader.ReadMessage();
+
+            while (inner.Position < inner.Length)
+            {
+                var message = inner.ReadMessage();
+                if (message.Tag == 1)
+                {
+                    Assert.Equal("HiTest1", message.ReadString());
+
+                    var messageSub = message.ReadMessage();
+                    if (messageSub.Tag == 2)
+                    {
+                        Assert.Equal("RemoveMe!", messageSub.ReadString());
+
+                        // Remove this message.
+                        // inner.RemoveMessage(messageSub);
+                    }
+                }
+                else if (message.Tag == 2)
+                {
+                    Assert.Equal("HiTest2", message.ReadString());
+                }
+            }
+
+            // Check if the magic was successful.
+            Assert.Equal(messageExpected.ToByteArray(true), messageWriter.ToByteArray(true));
+        }
+
         [Fact]
         public void GetLittleEndian()
         {
             Assert.True(MessageWriter.IsLittleEndian());
         }
     }
-}
\ No newline at end of file
+}