From 28bf0b445bb0b1b4699c6d95a85d4dc24997c437 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Thu, 23 Mar 2017 09:18:49 -0700 Subject: [PATCH] add some very simple, fairly stupid, benchmarks (#322) --- .gitignore | 1 + SignalR.sln | 7 +++ build/repo.props | 1 + .../Internal/Formatters/TextMessageParser.cs | 2 +- .../ByteArrayExtensions.cs | 6 ++- .../CoreConfig.cs | 32 ++++++++++++ .../MessageParserBenchmark.cs | 51 +++++++++++++++++++ ....AspNetCore.SignalR.Microbenchmarks.csproj | 20 ++++++++ .../Program.cs | 13 +++++ .../Formatters/BinaryMessageParserTests.cs | 4 +- .../Formatters/TextMessageParserTests.cs | 19 +++---- ...oft.AspNetCore.Sockets.Common.Tests.csproj | 1 + 12 files changed, 143 insertions(+), 14 deletions(-) rename test/{Microsoft.AspNetCore.Sockets.Common.Tests => Common}/ByteArrayExtensions.cs (92%) create mode 100644 test/Microsoft.AspNetCore.SignalR.Microbenchmarks/CoreConfig.cs create mode 100644 test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs create mode 100644 test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj create mode 100644 test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Program.cs diff --git a/.gitignore b/.gitignore index f159127ff0..c00b373fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ site.min.css signalr-client/ dist/ global.json +BenchmarkDotNet.Artifacts/ diff --git a/SignalR.sln b/SignalR.sln index 63c6f13670..abdb147a91 100644 --- a/SignalR.sln +++ b/SignalR.sln @@ -73,6 +73,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client-ts", "client-ts", "{ client-ts\package.json = client-ts\package.json EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.SignalR.Microbenchmarks", "test\Microsoft.AspNetCore.SignalR.Microbenchmarks\Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj", "{96771B3F-4D18-41A7-A75B-FF38E76AAC89}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -175,6 +177,10 @@ Global {333526A4-633B-491A-AC45-CC62A0012D1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {333526A4-633B-491A-AC45-CC62A0012D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {333526A4-633B-491A-AC45-CC62A0012D1C}.Release|Any CPU.Build.0 = Release|Any CPU + {96771B3F-4D18-41A7-A75B-FF38E76AAC89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96771B3F-4D18-41A7-A75B-FF38E76AAC89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96771B3F-4D18-41A7-A75B-FF38E76AAC89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96771B3F-4D18-41A7-A75B-FF38E76AAC89}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -205,5 +211,6 @@ Global {B0D32729-48AA-4841-B52A-2A61B60EED61} = {6A35B453-52EC-48AF-89CA-D4A69800F131} {333526A4-633B-491A-AC45-CC62A0012D1C} = {3A76C5A2-79ED-49BC-8BDC-6A3A766FFA1B} {6CEC3DC2-5B01-45A8-8F0D-8531315DA90B} = {6A35B453-52EC-48AF-89CA-D4A69800F131} + {96771B3F-4D18-41A7-A75B-FF38E76AAC89} = {6A35B453-52EC-48AF-89CA-D4A69800F131} EndGlobalSection EndGlobal diff --git a/build/repo.props b/build/repo.props index af0b15bb6f..eda599a342 100644 --- a/build/repo.props +++ b/build/repo.props @@ -2,6 +2,7 @@ + diff --git a/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/TextMessageParser.cs b/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/TextMessageParser.cs index 764d4334d0..a730b03d85 100644 --- a/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/TextMessageParser.cs +++ b/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/TextMessageParser.cs @@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters else { // Copy as much as possible from the Unread buffer - var toCopy = Math.Min(_state.Length, buffer.Unread.Length); + var toCopy = Math.Min(_state.Length - _state.Read, buffer.Unread.Length); buffer.Unread.Slice(0, toCopy).CopyTo(_state.Payload.Slice(_state.Read)); _state.Read += toCopy; buffer.Advance(toCopy); diff --git a/test/Microsoft.AspNetCore.Sockets.Common.Tests/ByteArrayExtensions.cs b/test/Common/ByteArrayExtensions.cs similarity index 92% rename from test/Microsoft.AspNetCore.Sockets.Common.Tests/ByteArrayExtensions.cs rename to test/Common/ByteArrayExtensions.cs index 5e5ad459f4..9b02403b27 100644 --- a/test/Microsoft.AspNetCore.Sockets.Common.Tests/ByteArrayExtensions.cs +++ b/test/Common/ByteArrayExtensions.cs @@ -3,7 +3,6 @@ using System.Buffers; using System.Collections.Generic; -using System.Collections.Sequences; namespace System { @@ -11,6 +10,11 @@ namespace System { public static ReadOnlyBytes ToChunkedReadOnlyBytes(this byte[] data, int chunkSize) { + if (chunkSize == 0) + { + return new ReadOnlyBytes(data); + } + var chunks = new List(); for (var i = 0; i < data.Length; i += chunkSize) { diff --git a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/CoreConfig.cs b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/CoreConfig.cs new file mode 100644 index 0000000000..c4e615ae2e --- /dev/null +++ b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/CoreConfig.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Validators; + +namespace Microsoft.AspNetCore.SignalR.Microbenchmarks +{ + public class CoreConfig : ManualConfig + { + public CoreConfig() + { + Add(JitOptimizationsValidator.FailOnError); + Add(MemoryDiagnoser.Default); + Add(StatisticColumn.OperationsPerSecond); + + Add(Job.Default + .With(Runtime.Core) + .WithRemoveOutliers(false) + .With(new GcMode() { Server = true }) + .With(RunStrategy.Throughput) + .WithLaunchCount(3) + .WithWarmupCount(5) + .WithTargetCount(10)); + } + } +} diff --git a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs new file mode 100644 index 0000000000..443bd3e0f4 --- /dev/null +++ b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs @@ -0,0 +1,51 @@ +using System; +using System.Buffers; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Sockets; +using Microsoft.AspNetCore.Sockets.Internal.Formatters; +using Microsoft.AspNetCore.Sockets.Tests.Internal; + +namespace Microsoft.AspNetCore.SignalR.Microbenchmarks +{ + [Config(typeof(CoreConfig))] + public class MessageParserBenchmark + { + private static readonly Random Random = new Random(); + private readonly MessageParser _parser = new MessageParser(); + private ReadOnlyBytes _input; + private byte[] _buffer; + + [Params(32, 64)] + public int ChunkSize { get; set; } + + [Params(64, 128)] + public int MessageLength { get; set; } + + [Params(MessageFormat.Text, MessageFormat.Binary)] + public MessageFormat Format { get; set; } + + [Setup] + public void Setup() + { + _buffer = new byte[MessageLength]; + Random.NextBytes(_buffer); + var message = new Message(_buffer, MessageType.Binary); + var output = new ArrayOutput(MessageLength + 32); + if (!MessageFormatter.TryWriteMessage(message, output, Format)) + { + throw new InvalidOperationException("Failed to format message"); + } + _input = output.ToArray().ToChunkedReadOnlyBytes(ChunkSize); + } + + [Benchmark] + public void SingleBinaryMessage() + { + var reader = new BytesReader(_input); + if (!_parser.TryParseMessage(ref reader, Format, out _)) + { + throw new InvalidOperationException("Failed to parse"); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj new file mode 100644 index 0000000000..e4b3bd3e59 --- /dev/null +++ b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj @@ -0,0 +1,20 @@ + + + + + + Exe + netcoreapp1.1 + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Program.cs b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Program.cs new file mode 100644 index 0000000000..69d514a523 --- /dev/null +++ b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Program.cs @@ -0,0 +1,13 @@ +using System.Reflection; +using BenchmarkDotNet.Running; + +namespace Microsoft.AspNetCore.SignalR.Microbenchmarks +{ + class Program + { + static void Main(string[] args) + { + BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/BinaryMessageParserTests.cs b/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/BinaryMessageParserTests.cs index 8909729571..70cc7e3c6e 100644 --- a/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/BinaryMessageParserTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/BinaryMessageParserTests.cs @@ -66,9 +66,7 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters /* body: */ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6F, 0x72 }; var parser = new MessageParser(); - var buffer = chunkSize > 0 ? - encoded.ToChunkedReadOnlyBytes(chunkSize) : - new ReadOnlyBytes(encoded); + var buffer = encoded.ToChunkedReadOnlyBytes(chunkSize); var reader = new BytesReader(buffer); var messages = new List(); diff --git a/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/TextMessageParserTests.cs b/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/TextMessageParserTests.cs index 330bafec91..f9846d3f9f 100644 --- a/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/TextMessageParserTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/TextMessageParserTests.cs @@ -14,18 +14,19 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters public class TextMessageParserTests { [Theory] - [InlineData("0:T:;", MessageType.Text, "")] - [InlineData("3:T:ABC;", MessageType.Text, "ABC")] - [InlineData("11:T:A\nR\rC\r\n;DEF;", MessageType.Text, "A\nR\rC\r\n;DEF")] - [InlineData("0:C:;", MessageType.Close, "")] - [InlineData("17:C:Connection Closed;", MessageType.Close, "Connection Closed")] - [InlineData("0:E:;", MessageType.Error, "")] - [InlineData("12:E:Server Error;", MessageType.Error, "Server Error")] - public void ReadTextMessage(string encoded, MessageType messageType, string payload) + [InlineData(0, "0:T:;", MessageType.Text, "")] + [InlineData(0, "3:T:ABC;", MessageType.Text, "ABC")] + [InlineData(0, "11:T:A\nR\rC\r\n;DEF;", MessageType.Text, "A\nR\rC\r\n;DEF")] + [InlineData(0, "0:C:;", MessageType.Close, "")] + [InlineData(0, "17:C:Connection Closed;", MessageType.Close, "Connection Closed")] + [InlineData(0, "0:E:;", MessageType.Error, "")] + [InlineData(0, "12:E:Server Error;", MessageType.Error, "Server Error")] + [InlineData(4, "12:T:Hello, World;", MessageType.Text, "Hello, World")] + public void ReadTextMessage(int chunkSize, string encoded, MessageType messageType, string payload) { var parser = new MessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); - var reader = new BytesReader(buffer); + var reader = new BytesReader(buffer.ToChunkedReadOnlyBytes(chunkSize)); Assert.True(parser.TryParseMessage(ref reader, MessageFormat.Text, out var message)); Assert.Equal(reader.Index, buffer.Length); diff --git a/test/Microsoft.AspNetCore.Sockets.Common.Tests/Microsoft.AspNetCore.Sockets.Common.Tests.csproj b/test/Microsoft.AspNetCore.Sockets.Common.Tests/Microsoft.AspNetCore.Sockets.Common.Tests.csproj index 2fef7c6ca7..5b48918f7a 100644 --- a/test/Microsoft.AspNetCore.Sockets.Common.Tests/Microsoft.AspNetCore.Sockets.Common.Tests.csproj +++ b/test/Microsoft.AspNetCore.Sockets.Common.Tests/Microsoft.AspNetCore.Sockets.Common.Tests.csproj @@ -12,6 +12,7 @@ +