Files
natsdotnet/tests/NATS.Server.Benchmark.Tests/CorePubSub/SubListMatchBenchmarks.cs
2026-03-13 10:08:50 -04:00

113 lines
4.1 KiB
C#

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("");
}
}