docs: record SubList allocation strategy

This commit is contained in:
Joseph Doherty
2026-03-13 10:08:50 -04:00
parent 0126234fa6
commit d1f22255d7
3 changed files with 231 additions and 170 deletions

View File

@@ -0,0 +1,112 @@
using System.Diagnostics;
using NATS.Server.Benchmark.Tests.Harness;
using NATS.Server.Subscriptions;
using Xunit.Abstractions;
namespace NATS.Server.Benchmark.Tests.CorePubSub;
public class SubListMatchBenchmarks(ITestOutputHelper output)
{
[Fact]
[Trait("Category", "Benchmark")]
public void SubListExactMatch_128Subjects()
{
using var subList = new SubList();
for (var i = 0; i < 128; i++)
subList.Insert(new Subscription { Subject = $"bench.exact.{i}", Sid = i.ToString() });
var (result, allocatedBytes) = Measure("SubList Exact Match (128 subjects)", "DotNet", "bench.exact.64".Length, 250_000, () =>
{
_ = subList.Match("bench.exact.64");
});
BenchmarkResultWriter.WriteSingle(output, result);
WriteAllocationSummary(allocatedBytes, result.TotalMessages);
}
[Fact]
[Trait("Category", "Benchmark")]
public void SubListWildcardMatch_FanIn()
{
using var subList = new SubList();
subList.Insert(new Subscription { Subject = "orders.created", Sid = "1" });
subList.Insert(new Subscription { Subject = "orders.*", Sid = "2" });
subList.Insert(new Subscription { Subject = "orders.>", Sid = "3" });
subList.Insert(new Subscription { Subject = "orders.created.us", Sid = "4" });
var (result, allocatedBytes) = Measure("SubList Wildcard Match", "DotNet", "orders.created".Length, 250_000, () =>
{
_ = subList.Match("orders.created");
});
BenchmarkResultWriter.WriteSingle(output, result);
WriteAllocationSummary(allocatedBytes, result.TotalMessages);
}
[Fact]
[Trait("Category", "Benchmark")]
public void SubListQueueMatch_MergedGroups()
{
using var subList = new SubList();
subList.Insert(new Subscription { Subject = "jobs.run", Queue = "workers", Sid = "1" });
subList.Insert(new Subscription { Subject = "jobs.*", Queue = "workers", Sid = "2" });
subList.Insert(new Subscription { Subject = "jobs.>", Queue = "audit", Sid = "3" });
var (result, allocatedBytes) = Measure("SubList Queue Match", "DotNet", "jobs.run".Length, 250_000, () =>
{
_ = subList.Match("jobs.run");
});
BenchmarkResultWriter.WriteSingle(output, result);
WriteAllocationSummary(allocatedBytes, result.TotalMessages);
}
[Fact]
[Trait("Category", "Benchmark")]
public void SubListRemoteInterest_WildcardLookup()
{
using var subList = new SubList();
for (var i = 0; i < 64; i++)
subList.ApplyRemoteSub(new RemoteSubscription($"remote.{i}.*", null, $"r{i}", "A"));
var (result, allocatedBytes) = Measure("SubList Remote Interest", "DotNet", "remote.42.created".Length, 250_000, () =>
{
_ = subList.HasRemoteInterest("A", "remote.42.created");
});
BenchmarkResultWriter.WriteSingle(output, result);
WriteAllocationSummary(allocatedBytes, result.TotalMessages);
}
private static (BenchmarkResult Result, long AllocatedBytes) Measure(string name, string serverType, int bytesPerOperation, int iterations, Action operation)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
for (var i = 0; i < 1_000; i++)
operation();
var before = GC.GetAllocatedBytesForCurrentThread();
var sw = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
operation();
sw.Stop();
var allocatedBytes = GC.GetAllocatedBytesForCurrentThread() - before;
return (new BenchmarkResult
{
Name = name,
ServerType = serverType,
TotalMessages = iterations,
TotalBytes = (long)iterations * bytesPerOperation,
Duration = sw.Elapsed,
}, allocatedBytes);
}
private void WriteAllocationSummary(long allocatedBytes, long iterations)
{
output.WriteLine($"Allocated: {allocatedBytes:N0} B total | {allocatedBytes / (double)iterations:F2} B/op");
output.WriteLine("");
}
}

View File

@@ -23,4 +23,8 @@
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\NATS.Server\NATS.Server.csproj" />
</ItemGroup>
</Project>