Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small irc parsing performance improvements #39

Merged
merged 10 commits into from
Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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