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 @@
+