Skip to content

Commit

Permalink
Small irc parsing performance improvements (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
Foretack committed Oct 8, 2023
1 parent 39abd47 commit 15687e1
Show file tree
Hide file tree
Showing 22 changed files with 562 additions and 463 deletions.
53 changes: 53 additions & 0 deletions MiniTwitch.Irc.Benchmarks/Benchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Text;
using BenchmarkDotNet.Attributes;
using MiniTwitch.Irc.Internal.Models;

namespace MiniTwitch.Irc.Benchmarks;

[MemoryDiagnoser(false)]
public class Benchmarks
{
private byte[][] utf8Lines = null!;
private readonly IrcClient _client = new(op => op.Anonymous = true);

[Params(1, 100, 1000)]
public int LineCount { get; set; }

[GlobalSetup]
public void AddData()
{
utf8Lines = File.ReadAllText("data.txt").Split('\0').Take(LineCount).Select(Encoding.UTF8.GetBytes).ToArray();
Console.WriteLine(utf8Lines.Length);
}

[Benchmark]
public void Create()
{
foreach (var line in utf8Lines.AsSpan())
{
_ = new IrcMessage(line);
}
}

[Benchmark]
public void Create_Parse()
{
foreach (var line in utf8Lines.AsSpan())
{
var msg = new IrcMessage(line);
_ = msg.ParseTags();
_ = msg.GetChannel();
_ = msg.GetContent();
_ = msg.GetUsername();
}
}

[Benchmark]
public void Create_Parse_Map_Send()
{
foreach (var line in utf8Lines.AsSpan())
{
_client.Parse(line);
}
}
}
22 changes: 22 additions & 0 deletions MiniTwitch.Irc.Benchmarks/MiniTwitch.Irc.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MiniTwitch.Irc\MiniTwitch.Irc.csproj" />
</ItemGroup>

<ItemGroup>
<Content Include="data.txt" CopyToOutputDirectory="Always" />
</ItemGroup>

</Project>
4 changes: 4 additions & 0 deletions MiniTwitch.Irc.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using BenchmarkDotNet.Running;
using MiniTwitch.Irc.Benchmarks;

BenchmarkRunner.Run<Benchmarks>();
Binary file added MiniTwitch.Irc.Benchmarks/data.txt
Binary file not shown.
34 changes: 12 additions & 22 deletions MiniTwitch.Irc.Test/CommandParsingTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text;
using MiniTwitch.Irc.Internal.Enums;
using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
using Xunit;

Expand All @@ -10,78 +11,67 @@ public class CommandParsingTests
public void Parse_PRIVMSG()
{
string raw = "@badge-info=subscriber/42;badges=subscriber/42,glhf-pledge/1;color=#F97304;display-name=zneix;emotes=;first-msg=0;flags=;id=ffc033d5-862c-413c-8422-72a59aa1b0f1;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1678752306574;turbo=0;user-id=99631238;user-type= :[email protected] PRIVMSG #pajlada :btw DFantasy, have you gotten an unused Dragon Hunter Crossbow just laying around by any chance? kkOna";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.PRIVMSG, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.PRIVMSG, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_USERNOTICE()
{
string raw = "@badge-info=subscriber/15;badges=subscriber/12;color=#BAFF2F;display-name=VOJTERIS;emotes=;flags=;id=267bffe6-534c-4ddc-a11c-d32b242d8734;login=vojteris;mod=0;msg-id=resub;msg-param-cumulative-months=15;msg-param-months=0;msg-param-multimonth-duration=0;msg-param-multimonth-tenure=0;msg-param-should-share-streak=0;msg-param-sub-plan-name=look\\sat\\sthose\\sshitty\\semotes,\\srip\\s$5\\sLUL;msg-param-sub-plan=1000;msg-param-was-gifted=false;room-id=11148817;subscriber=1;system-msg=VOJTERIS\\ssubscribed\\sat\\sTier\\s1.\\sThey've\\ssubscribed\\sfor\\s15\\smonths!;tmi-sent-ts=1678743959736;user-id=75436142;user-type= :tmi.twitch.tv USERNOTICE #pajlada :egFors TeaTime";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.USERNOTICE, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.USERNOTICE, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_CLEARCHAT()
{
string raw = "@ban-duration=30;room-id=11148817;target-user-id=783267696;tmi-sent-ts=1678712873805 :tmi.twitch.tv CLEARCHAT #pajlada :occluder";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.CLEARCHAT, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.CLEARCHAT, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_WHISPER()
{
string raw = "@badges=;color=#2E8B57;display-name=pajbot;emotes=25:7-11;message-id=7;thread-id=82008718_783267696;turbo=0;user-id=82008718;user-type= :[email protected] WHISPER occluder :Riftey Kappa";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.WHISPER, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.WHISPER, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_ROOMSTATE()
{
string raw = "@emote-only=0;followers-only=-1;r9k=0;room-id=783267696;slow=0;subs-only=0 :tmi.twitch.tv ROOMSTATE #occluder";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.ROOMSTATE, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.ROOMSTATE, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_NOTICE()
{
string raw = "@msg-id=msg_channel_suspended :tmi.twitch.tv NOTICE #foretack :This channel does not exist or has been suspended.";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.NOTICE, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.NOTICE, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_CLEARMSG()
{
string raw = "@login=occluder;room-id=;target-msg-id=55dc74c9-a6b2-4443-9b68-3446a5ddb7ed;tmi-sent-ts=1678798254260 :tmi.twitch.tv CLEARMSG #occluder :@emote-only=0;followers-only=-1;r9k=0;room-id=783267696;slow=0;subs-only=0 :tmi.twitch.tv ROOMSTATE #occluder ";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.CLEARMSG, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.CLEARMSG, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_USERSTATE()
{
string raw = "@badge-info=;badges=broadcaster/1;color=#F2647B;display-name=occluder;emote-sets=0,4236,15961,19194,376284588,385292559,477339272,5fc5e142-d394-481f-a561-5406c2ba3ef0,e21484e8-cf11-48b1-8b67-c180fa39f926;mod=0;subscriber=0;user-type= :tmi.twitch.tv USERSTATE #occluder";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.USERSTATE, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.USERSTATE, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_RECONNECT()
{
string raw = ":tmi.twitch.tv RECONNECT";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.RECONNECT, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.RECONNECT, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_PING()
{
string raw = "PING :tmi.twitch.tv";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.PING, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.PING, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_PONG()
{
string raw = "PONG :tmi.twitch.tv";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
Assert.Equal(IrcCommand.PONG, IrcParsing.ParseCommand(span).cmd);
Assert.Equal(IrcCommand.PONG, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_Multiple()
Expand Down
35 changes: 9 additions & 26 deletions MiniTwitch.Irc.Test/IrcParsingTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
using Xunit;

Expand All @@ -9,18 +10,7 @@ public class IrcParsingTests
public void Find_Channel()
{
string raw = ":[email protected] JOIN #bar";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
string channel = IrcParsing.FindChannel(span);

Assert.Equal("bar", channel);
}

[Fact]
public void Find_Channel_AnySeparator()
{
string raw = ":[email protected] JOIN #bar\r\n";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
string channel = IrcParsing.FindChannel(span, true);
string channel = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetChannel();

Assert.Equal("bar", channel);
}
Expand All @@ -29,8 +19,7 @@ public void Find_Channel_AnySeparator()
public void Find_Content()
{
string raw = "@badge-info=subscriber/11;badges=subscriber/6;color=#F2647B;display-name=occluder;emotes=;first-msg=0;flags=;id=e674e393-1230-4a89-bebc-fae1f925e52c;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1680255594264;turbo=0;user-id=783267696;user-type= :[email protected] PRIVMSG #pajlada :Are you on some dank browser jammehcow";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
string content = IrcParsing.FindContent(span).Content;
string content = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetContent().Content;

Assert.Equal("Are you on some dank browser jammehcow", content);
}
Expand All @@ -39,8 +28,7 @@ public void Find_Content()
public void Find_Content_Empty()
{
string raw = "@badge-info=subscriber/5;badges=subscriber/3;color=#5F9EA0;display-name=Syn993;emotes=;flags=;id=401d17b8-363a-4f63-85c8-cd5996fbd4e0;login=syn993;mod=0;msg-id=resub;msg-param-cumulative-months=5;msg-param-months=0;msg-param-multimonth-duration=0;msg-param-multimonth-tenure=0;msg-param-should-share-streak=1;msg-param-streak-months=4;msg-param-sub-plan-name=Channel\\sSubscription\\s(mandeow);msg-param-sub-plan=1000;msg-param-was-gifted=false;room-id=128856353;subscriber=1;system-msg=Syn993\\ssubscribed\\sat\\sTier\\s1.\\sThey've\\ssubscribed\\sfor\\s5\\smonths,\\scurrently\\son\\sa\\s4\\smonth\\sstreak!;tmi-sent-ts=1678873100296;user-id=79085174;user-type= :tmi.twitch.tv USERNOTICE #mande";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
string content = IrcParsing.FindContent(span, maybeEmpty: true).Content;
string content = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetContent().Content;

Assert.Equal(string.Empty, content);
}
Expand All @@ -49,19 +37,16 @@ public void Find_Content_Empty()
public void Find_Content_Action()
{
string raw = "@badge-info=subscriber/1;badges=subscriber/0;color=#9ACD32;display-name=FeelsCzechMan;emotes=425671:13-20;first-msg=0;flags=;id=0012619e-8e14-4d51-93c9-e9d6fd5a178b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1679730451594;turbo=0;user-id=875745889;user-type= :[email protected] PRIVMSG #pajlada :\u0001ACTION FeelsDankMan PowerUpR DANK WAVE▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂\u0001";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
(string content, bool action) = IrcParsing.FindContent(span, maybeAction: true);

Assert.True(action);
(string content, bool action) = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetContent(maybeAction: true);
Assert.Equal("FeelsDankMan PowerUpR DANK WAVE▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂", content);
Assert.True(action);
}

[Fact]
public void Find_Content_InvalidAction()
{
string raw = "@badge-info=subscriber/1;badges=subscriber/0;color=#9ACD32;display-name=FeelsCzechMan;emotes=425671:13-20;first-msg=0;flags=;id=0012619e-8e14-4d51-93c9-e9d6fd5a178b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1679730451594;turbo=0;user-id=875745889;user-type= :[email protected] PRIVMSG #pajlada :\u0001ACTION \u0001";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
(string content, bool action) = IrcParsing.FindContent(span, maybeAction: true);
(string content, bool action) = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetContent();

Assert.False(action);
Assert.Equal("\u0001ACTION \u0001", content);
Expand All @@ -71,8 +56,7 @@ public void Find_Content_InvalidAction()
public void Find_Username()
{
string raw = "@badge-info=subscriber/1;badges=subscriber/0;color=#9ACD32;display-name=FeelsCzechMan;emotes=425671:13-20;first-msg=0;flags=;id=0012619e-8e14-4d51-93c9-e9d6fd5a178b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1679730451594;turbo=0;user-id=875745889;user-type= :[email protected] PRIVMSG #pajlada :\u0001ACTION FeelsDankMan PowerUpR DANK WAVE▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂\u0001";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
string username = IrcParsing.FindUsername(span);
string username = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetUsername();

Assert.Equal("feelsczechman", username);
}
Expand All @@ -81,8 +65,7 @@ public void Find_Username()
public void Find_Username_NoTags()
{
string raw = ":[email protected] JOIN #bar";
ReadOnlySpan<byte> span = Encoding.UTF8.GetBytes(raw).AsSpan();
string username = IrcParsing.FindUsername(span, true);
string username = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetUsername();

Assert.Equal("pajapajapaja", username);
}
Expand Down
1 change: 1 addition & 0 deletions MiniTwitch.Irc/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("MiniTwitch.Irc.Test")]
[assembly: InternalsVisibleTo("MiniTwitch.Irc.Benchmarks")]
66 changes: 66 additions & 0 deletions MiniTwitch.Irc/Internal/Enums/Tags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace MiniTwitch.Irc.Internal.Enums;

internal enum Tags
{
Id = 205,
R9K = 278,
Mod = 320,
Vip = 335,
Bits = 434,
Slow = 453,
Flags = 525,
Login = 537,
Color = 543,
Turbo = 556,
MsgId = 577,
Badges = 614,
Emotes = 653,
RoomId = 695,
UserId = 697,
ThreadId = 882,
FirstMsg = 924,
SubsOnly = 940,
UserType = 942,
BadgeInfo = 972,
MessageId = 991,
EmoteSets = 1030,
EmoteOnly = 1033,
SystemMsg = 1049,
Subscriber = 1076,
TmiSentTs = 1093,
ClientNonce = 1215,
BanDuration = 1220,
DisplayName = 1220,
TargetMsgId = 1269,
TargetUserId = 1389,
FollowersOnly = 1484,
MsgParamColor = 1489,
MsgParamMonths = 1611,
MsgParamSubPlan = 1748,
ReturningChatter = 1782,
ReplyParentMsgId = 1873,
ReplyParentUserId = 1993,
MsgParamSenderName = 2049,
MsgParamGiftMonths = 2082,
ReplyParentMsgBody = 2098,
MsgParamViewerCount = 2125,
PinnedChatPaidLevel = 2139,
MsgParamRecipientId = 2159,
MsgParamSenderLogin = 2169,
MsgParamSenderCount = 2185,
MsgParamSubPlanName = 2210,
PinnedChatPaidAmount = 2263,
MsgParamStreakMonths = 2306,
ReplyParentUserLogin = 2325,
MsgParamMassGiftCount = 2451,
PinnedChatPaidCurrency = 2478,
PinnedChatPaidExponent = 2484,
ReplyParentDisplayName = 2516,
ReplyThreadParentMsgId = 2550,
MsgParamCumulativeMonths = 2743,
MsgParamRecipientUserName = 2863,
MsgParamShouldShareStreak = 2872,
ReplyThreadParentUserLogin = 3002,
MsgparamRecipientDisplayName = 3174,
PinnedChatPaidIsSystemMessage = 3331,
}
Loading

0 comments on commit 15687e1

Please sign in to comment.