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