Skip to content

Commit e08be52

Browse files
committed
General performance improvments
1 parent a51cf66 commit e08be52

File tree

7 files changed

+149
-107
lines changed

7 files changed

+149
-107
lines changed

Benchmark.AspNetCore.ServerSentEvents/Benchmarks/ServerSentEventsServiceBenchmarks.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ public class ServerSentEventsServiceBenchmarks
1515
#region Fields
1616
private const int MULTIPLE_CLIENTS_COUNT = 10000;
1717

18+
private const string EVENT_TYPE = "Benchmark";
19+
private const string EVENT_DATA = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
20+
1821
private readonly ServerSentEventsClient _serverSentEventsClient;
1922
private readonly ServerSentEventsService _serverSentEventsService;
2023
private readonly ServerSentEvent _event = new ServerSentEvent
2124
{
2225
Id = Guid.NewGuid().ToString(),
23-
Type = "Benchmark",
24-
Data = new List<string> { "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }
26+
Type = EVENT_TYPE,
27+
Data = new List<string> { EVENT_DATA }
2528
};
2629
#endregion
2730

@@ -40,9 +43,27 @@ public ServerSentEventsServiceBenchmarks()
4043

4144
#region Benchmarks
4245
[Benchmark]
43-
public void SendEventAsync_SingleEvent_SingleClient()
46+
public Task SendEventAsync_SingleData_SingleClient()
47+
{
48+
return _serverSentEventsClient.SendEventAsync(EVENT_DATA);
49+
}
50+
51+
[Benchmark]
52+
public Task SendEventAsync_SingleEvent_SingleClient()
53+
{
54+
return _serverSentEventsClient.SendEventAsync(_event);
55+
}
56+
57+
[Benchmark]
58+
public Task ChangeReconnectIntervalAsync_SingleClient()
59+
{
60+
return _serverSentEventsClient.ChangeReconnectIntervalAsync(5000);
61+
}
62+
63+
[Benchmark]
64+
public Task SendEventAsync_SingleData_MultipleClients()
4465
{
45-
_serverSentEventsClient.SendEvent(_event);
66+
return _serverSentEventsService.SendEventAsync(EVENT_DATA);
4667
}
4768

4869
[Benchmark]

Lib.AspNetCore.ServerSentEvents/Internals/RawServerSentEvent.cs

Lines changed: 0 additions & 48 deletions
This file was deleted.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace Lib.AspNetCore.ServerSentEvents.Internals
2+
{
3+
internal readonly struct ServerSentEventBytes
4+
{
5+
#region Properties
6+
internal byte[] Bytes { get; }
7+
8+
internal int BytesCount { get; }
9+
#endregion
10+
11+
#region Constructor
12+
internal ServerSentEventBytes(byte[] bytes, int bytesCount)
13+
: this()
14+
{
15+
Bytes = bytes;
16+
BytesCount = bytesCount;
17+
}
18+
#endregion
19+
}
20+
}

Lib.AspNetCore.ServerSentEvents/Internals/ServerSentEventsHelper.cs

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Text;
1+
using System;
2+
using System.Text;
3+
using System.Globalization;
24
using System.Threading.Tasks;
35
using Microsoft.AspNetCore.Http;
46

@@ -7,65 +9,130 @@ namespace Lib.AspNetCore.ServerSentEvents.Internals
79
internal static class ServerSentEventsHelper
810
{
911
#region Fields
12+
private const byte CR = 13;
13+
private const byte LF = 10;
14+
private const int CRLF_LENGTH = 2;
15+
1016
private static byte[] _sseRetryField = Encoding.UTF8.GetBytes(Constants.SSE_RETRY_FIELD);
1117
private static byte[] _sseIdField = Encoding.UTF8.GetBytes(Constants.SSE_ID_FIELD);
1218
private static byte[] _sseEventField = Encoding.UTF8.GetBytes(Constants.SSE_EVENT_FIELD);
1319
private static byte[] _sseDataField = Encoding.UTF8.GetBytes(Constants.SSE_DATA_FIELD);
14-
private static byte[] _endOfLine = new byte[] { 13, 10 };
1520
#endregion
1621

17-
#region HttpResponse Extensions
18-
internal static async Task AcceptSse(this HttpResponse response)
22+
#region Methods
23+
internal static Task AcceptAsync(this HttpResponse response)
1924
{
2025
response.ContentType = Constants.SSE_CONTENT_TYPE;
21-
await response.Body.FlushAsync();
26+
return response.Body.FlushAsync();
27+
}
28+
29+
internal static Task WriteAsync(this HttpResponse response, ServerSentEventBytes serverSentEvent)
30+
{
31+
return response.Body.WriteAsync(serverSentEvent.Bytes, 0, serverSentEvent.BytesCount);
32+
}
33+
34+
internal static ServerSentEventBytes GetReconnectIntervalBytes(uint reconnectInterval)
35+
{
36+
string reconnectIntervalStringified = reconnectInterval.ToString(CultureInfo.InvariantCulture);
37+
38+
byte[] bytes = new byte[GetFieldMaxBytesCount(_sseRetryField, reconnectIntervalStringified) + CRLF_LENGTH];
39+
int bytesCount = GetFieldBytes(_sseRetryField, reconnectIntervalStringified, bytes, 0);
40+
41+
bytes[bytesCount++] = CR;
42+
bytes[bytesCount++] = LF;
43+
44+
return new ServerSentEventBytes(bytes, bytesCount);
2245
}
2346

24-
internal static async Task WriteSseRetryAsync(this HttpResponse response, byte[] reconnectInterval)
47+
internal static ServerSentEventBytes GetEventBytes(string text)
2548
{
26-
await response.WriteSseEventFieldAsync(_sseRetryField, reconnectInterval);
27-
await response.WriteSseEventBoundaryAsync();
49+
byte[] bytes = new byte[GetFieldMaxBytesCount(_sseDataField, text) + CRLF_LENGTH];
50+
int bytesCount = GetFieldBytes(_sseDataField, text, bytes, 0);
51+
52+
bytes[bytesCount++] = CR;
53+
bytes[bytesCount++] = LF;
54+
55+
return new ServerSentEventBytes(bytes, bytesCount);
2856
}
2957

30-
internal static async Task WriteSseEventAsync(this HttpResponse response, byte[] data)
58+
internal static ServerSentEventBytes GetEventBytes(ServerSentEvent serverSentEvent)
3159
{
32-
await response.WriteSseEventFieldAsync(_sseDataField, data);
33-
await response.WriteSseEventBoundaryAsync();
60+
int bytesCount = 0;
61+
byte[] bytes = new byte[GetEventMaxBytesCount(serverSentEvent)];
62+
63+
if (!String.IsNullOrWhiteSpace(serverSentEvent.Id))
64+
{
65+
bytesCount = GetFieldBytes(_sseIdField, serverSentEvent.Id, bytes, bytesCount);
66+
}
67+
68+
if (!String.IsNullOrWhiteSpace(serverSentEvent.Type))
69+
{
70+
bytesCount = GetFieldBytes(_sseEventField, serverSentEvent.Type, bytes, bytesCount);
71+
}
72+
73+
if (serverSentEvent.Data != null)
74+
{
75+
for (int dataItemIndex = 0; dataItemIndex < serverSentEvent.Data.Count; dataItemIndex++)
76+
{
77+
if (serverSentEvent.Data[dataItemIndex] != null)
78+
{
79+
bytesCount = GetFieldBytes(_sseDataField, serverSentEvent.Data[dataItemIndex], bytes, bytesCount);
80+
}
81+
}
82+
}
83+
84+
bytes[bytesCount++] = CR;
85+
bytes[bytesCount++] = LF;
86+
87+
return new ServerSentEventBytes(bytes, bytesCount);
3488
}
3589

36-
internal static async Task WriteSseEventAsync(this HttpResponse response, RawServerSentEvent serverSentEvent)
90+
private static int GetEventMaxBytesCount(ServerSentEvent serverSentEvent)
3791
{
38-
if (serverSentEvent.Id != null)
92+
int bytesCount = CRLF_LENGTH;
93+
94+
if (!String.IsNullOrWhiteSpace(serverSentEvent.Id))
3995
{
40-
await response.WriteSseEventFieldAsync(_sseIdField, serverSentEvent.Id);
96+
bytesCount += GetFieldMaxBytesCount(_sseIdField, serverSentEvent.Id);
4197
}
4298

43-
if (serverSentEvent.Type != null)
99+
if (!String.IsNullOrWhiteSpace(serverSentEvent.Type))
44100
{
45-
await response.WriteSseEventFieldAsync(_sseEventField, serverSentEvent.Type);
101+
bytesCount += GetFieldMaxBytesCount(_sseEventField, serverSentEvent.Type);
46102
}
47103

48104
if (serverSentEvent.Data != null)
49105
{
50-
for (int i = 0; i < serverSentEvent.Data.Count; i++)
106+
for (int dataItemIndex = 0; dataItemIndex < serverSentEvent.Data.Count; dataItemIndex++)
51107
{
52-
await response.WriteSseEventFieldAsync(_sseDataField, serverSentEvent.Data[i]);
108+
if (serverSentEvent.Data[dataItemIndex] != null)
109+
{
110+
bytesCount += GetFieldMaxBytesCount(_sseDataField, serverSentEvent.Data[dataItemIndex]);
111+
}
53112
}
54113
}
55114

56-
await response.WriteSseEventBoundaryAsync();
115+
return bytesCount;
57116
}
58117

59-
private static async Task WriteSseEventFieldAsync(this HttpResponse response, byte[] field, byte[] data)
118+
private static int GetFieldBytes(byte[] field, string data, byte[] bytes, int bytesCount)
60119
{
61-
await response.Body.WriteAsync(field, 0, field.Length);
62-
await response.Body.WriteAsync(data, 0, data.Length);
63-
await response.Body.WriteAsync(_endOfLine, 0, _endOfLine.Length);
120+
for (int fieldIndex = 0; fieldIndex < field.Length; fieldIndex++)
121+
{
122+
bytes[bytesCount++] = field[fieldIndex];
123+
}
124+
125+
bytesCount += Encoding.UTF8.GetBytes(data, 0, data.Length, bytes, bytesCount);
126+
127+
bytes[bytesCount++] = CR;
128+
bytes[bytesCount++] = LF;
129+
130+
return bytesCount;
64131
}
65132

66-
private static Task WriteSseEventBoundaryAsync(this HttpResponse response)
133+
private static int GetFieldMaxBytesCount(byte[] field, string data)
67134
{
68-
return response.Body.WriteAsync(_endOfLine, 0, _endOfLine.Length);
135+
return field.Length + Encoding.UTF8.GetMaxByteCount(data.Length) + CRLF_LENGTH;
69136
}
70137
#endregion
71138
}

Lib.AspNetCore.ServerSentEvents/ServerSentEventsClient.cs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using System;
2-
using System.Text;
3-
using System.Globalization;
42
using System.Security.Claims;
53
using System.Threading.Tasks;
64
using Microsoft.AspNetCore.Http;
@@ -52,7 +50,7 @@ internal ServerSentEventsClient(Guid id, ClaimsPrincipal user, HttpResponse resp
5250
/// <returns>The task object representing the asynchronous operation.</returns>
5351
public Task SendEventAsync(string text)
5452
{
55-
return SendEventAsync(Encoding.UTF8.GetBytes(text));
53+
return SendAsync(ServerSentEventsHelper.GetEventBytes(text));
5654
}
5755

5856
/// <summary>
@@ -62,33 +60,19 @@ public Task SendEventAsync(string text)
6260
/// <returns>The task object representing the asynchronous operation.</returns>
6361
public Task SendEventAsync(ServerSentEvent serverSentEvent)
6462
{
65-
return SendEventAsync(new RawServerSentEvent(serverSentEvent));
63+
return SendAsync(ServerSentEventsHelper.GetEventBytes(serverSentEvent));
6664
}
6765

68-
internal Task SendEventAsync(byte[] data)
66+
internal Task SendAsync(ServerSentEventBytes serverSentEvent)
6967
{
7068
CheckIsConnected();
7169

72-
return _response.WriteSseEventAsync(data);
73-
}
74-
75-
internal Task SendEventAsync(RawServerSentEvent serverSentEvent)
76-
{
77-
CheckIsConnected();
78-
79-
return _response.WriteSseEventAsync(serverSentEvent);
70+
return _response.WriteAsync(serverSentEvent);
8071
}
8172

8273
internal Task ChangeReconnectIntervalAsync(uint reconnectInterval)
8374
{
84-
return ChangeReconnectIntervalAsync(Encoding.UTF8.GetBytes(reconnectInterval.ToString(CultureInfo.InvariantCulture)));
85-
}
86-
87-
internal Task ChangeReconnectIntervalAsync(byte[] reconnectInterval)
88-
{
89-
CheckIsConnected();
90-
91-
return _response.WriteSseRetryAsync(reconnectInterval);
75+
return SendAsync(ServerSentEventsHelper.GetReconnectIntervalBytes(reconnectInterval));
9276
}
9377

9478
private void CheckIsConnected()

Lib.AspNetCore.ServerSentEvents/ServerSentEventsMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public async Task Invoke(HttpContext context)
4545

4646
HandleContentEncoding(context);
4747

48-
await context.Response.AcceptSse();
48+
await context.Response.AcceptAsync();
4949

5050
ServerSentEventsClient client = new ServerSentEventsClient(Guid.NewGuid(), context.User, context.Response);
5151

Lib.AspNetCore.ServerSentEvents/ServerSentEventsService.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System;
22
using System.Linq;
3-
using System.Text;
4-
using System.Globalization;
53
using System.Threading.Tasks;
64
using System.Collections.Generic;
75
using System.Collections.Concurrent;
@@ -58,9 +56,9 @@ public Task ChangeReconnectIntervalAsync(uint reconnectInterval)
5856
{
5957
ReconnectInterval = reconnectInterval;
6058

61-
byte[] reconnectIntervalBytes = Encoding.UTF8.GetBytes(reconnectInterval.ToString(CultureInfo.InvariantCulture));
59+
ServerSentEventBytes reconnectIntervalBytes = ServerSentEventsHelper.GetReconnectIntervalBytes(reconnectInterval);
6260

63-
return ForAllClientsAsync(client => client.ChangeReconnectIntervalAsync(reconnectIntervalBytes));
61+
return ForAllClientsAsync(client => client.SendAsync(reconnectIntervalBytes));
6462
}
6563

6664
/// <summary>
@@ -70,9 +68,9 @@ public Task ChangeReconnectIntervalAsync(uint reconnectInterval)
7068
/// <returns>The task object representing the asynchronous operation.</returns>
7169
public Task SendEventAsync(string text)
7270
{
73-
byte[] data = Encoding.UTF8.GetBytes(text);
71+
ServerSentEventBytes serverSentEventBytes = ServerSentEventsHelper.GetEventBytes(text);
7472

75-
return ForAllClientsAsync(client => client.SendEventAsync(data));
73+
return ForAllClientsAsync(client => client.SendAsync(serverSentEventBytes));
7674
}
7775

7876
/// <summary>
@@ -82,9 +80,9 @@ public Task SendEventAsync(string text)
8280
/// <returns>The task object representing the asynchronous operation.</returns>
8381
public Task SendEventAsync(ServerSentEvent serverSentEvent)
8482
{
85-
RawServerSentEvent rawServerSentEvent = new RawServerSentEvent(serverSentEvent);
83+
ServerSentEventBytes serverSentEventBytes = ServerSentEventsHelper.GetEventBytes(serverSentEvent);
8684

87-
return ForAllClientsAsync(client => client.SendEventAsync(rawServerSentEvent));
85+
return ForAllClientsAsync(client => client.SendAsync(serverSentEventBytes));
8886
}
8987

9088
/// <summary>

0 commit comments

Comments
 (0)