From 29e25541723e684832c8504a6260afabcf6da59d Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 5 Oct 2023 15:58:51 +0100 Subject: [PATCH] Micro benchmarks tidy up (#145) * Added switcher so we can add more benchmarks * Added a benchmark comparing pub wait-until-sent option --- sandbox/MicroBenchmark/DefaultBench.cs | 169 +++++++++++++++++ sandbox/MicroBenchmark/MicroBenchmark.csproj | 2 +- sandbox/MicroBenchmark/Program.cs | 175 +----------------- .../SerializationBuffersBench.cs | 45 +++++ 4 files changed, 217 insertions(+), 174 deletions(-) create mode 100644 sandbox/MicroBenchmark/DefaultBench.cs create mode 100644 sandbox/MicroBenchmark/SerializationBuffersBench.cs diff --git a/sandbox/MicroBenchmark/DefaultBench.cs b/sandbox/MicroBenchmark/DefaultBench.cs new file mode 100644 index 000000000..717b42ab4 --- /dev/null +++ b/sandbox/MicroBenchmark/DefaultBench.cs @@ -0,0 +1,169 @@ +#pragma warning disable IDE0044 +using System.Text.Json; +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NATS.Client.Core; +using StackExchange.Redis; +using ZLogger; + +namespace MicroBenchmark; + +public struct MyVector3 +{ + public float X { get; set; } + + public float Y { get; set; } + + public float Z { get; set; } +} + +// var run = new DefaultRun(); +// await run.SetupAsync(); +// await run.RunBenchmark(); +// await run.RunStackExchangeRedis(); + +// await run.CleanupAsync(); +#pragma warning disable CS8618 + +[MemoryDiagnoser] +[ShortRunJob] +[PlainExporter] +public class DefaultBench +{ +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + private NatsConnection _connection; + private string _subject; + private ConnectionMultiplexer _redis; + private object _gate; + private Handler _handler; + private IDisposable _subscription = default!; + + [GlobalSetup] + public async Task SetupAsync() + { + var provider = new ServiceCollection() + .AddLogging(x => + { + x.ClearProviders(); + x.SetMinimumLevel(LogLevel.Information); + x.AddZLoggerConsole(); + }) + .BuildServiceProvider(); + + var loggerFactory = provider.GetRequiredService(); + var logger = loggerFactory.CreateLogger>(); + var options = NatsOpts.Default with + { + LoggerFactory = loggerFactory, + Echo = true, + Verbose = false, + }; + + _connection = new NATS.Client.Core.NatsConnection(options); + _subject = "foobar"; + await _connection.ConnectAsync(); + _gate = new object(); + _redis = StackExchange.Redis.ConnectionMultiplexer.Connect("localhost"); + + _handler = new Handler(); + + // subscription = connection.Subscribe(key, handler.Handle); + } + + // [Benchmark] + public async Task Nop() + { + await Task.Yield(); + } + + [Benchmark] + public async Task PublishAsync() + { + for (var i = 0; i < 1; i++) + { + await _connection.PublishAsync(_subject, default(MyVector3)); + } + } + + // [Benchmark] + public async Task PublishAsyncRedis() + { + for (var i = 0; i < 1; i++) + { + await _redis.GetDatabase().PublishAsync(_subject, JsonSerializer.Serialize(default(MyVector3))); + } + } + + // [Benchmark] + public void RunBenchmark() + { + const int count = 10000; + _handler.Gate = _gate; + _handler.Called = 0; + _handler.Max = count; + + for (var i = 0; i < count; i++) + { + _connection.PublishAsync(_subject, default(MyVector3)); + } + + lock (_gate) + { + // Monitor.Wait(gate); + Thread.Sleep(1000); + } + } + + // [Benchmark] + // public async Task RunStackExchangeRedis() + // { + // var tcs = new TaskCompletionSource(); + // var called = 0; + // redis.GetSubscriber().Subscribe(key.Key, (channel, v) => + // { + // if (Interlocked.Increment(ref called) == 1000) + // { + // tcs.TrySetResult(); + // } + // }); + + // for (int i = 0; i < 1000; i++) + // { + // _ = redis.GetDatabase().PublishAsync(key.Key, JsonSerializer.Serialize(new MyVector3()), StackExchange.Redis.CommandFlags.FireAndForget); + // } + + // await tcs.Task; + // } + [GlobalCleanup] + public async Task CleanupAsync() + { + _subscription?.Dispose(); + if (_connection != null) + { + await _connection.DisposeAsync(); + } + + _redis?.Dispose(); + } + + private class Handler + { +#pragma warning disable SA1401 + public int Called; + public int Max; + public object Gate; +#pragma warning restore SA1401 + + public void Handle(MyVector3 vec) + { + if (Interlocked.Increment(ref Called) == Max) + { + lock (Gate) + { + Monitor.PulseAll(Gate); + } + } + } + } +} diff --git a/sandbox/MicroBenchmark/MicroBenchmark.csproj b/sandbox/MicroBenchmark/MicroBenchmark.csproj index 62a7481cd..53579c16d 100644 --- a/sandbox/MicroBenchmark/MicroBenchmark.csproj +++ b/sandbox/MicroBenchmark/MicroBenchmark.csproj @@ -9,7 +9,7 @@ - + diff --git a/sandbox/MicroBenchmark/Program.cs b/sandbox/MicroBenchmark/Program.cs index 8cdd34abc..c9a046727 100644 --- a/sandbox/MicroBenchmark/Program.cs +++ b/sandbox/MicroBenchmark/Program.cs @@ -1,174 +1,3 @@ -#pragma warning disable IDE0044 +using BenchmarkDotNet.Running; -using System.Text.Json; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Exporters; -using BenchmarkDotNet.Jobs; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using NATS.Client.Core; -using StackExchange.Redis; -using ZLogger; - -var config = ManualConfig.CreateMinimumViable() - .AddDiagnoser(MemoryDiagnoser.Default) - .AddExporter(DefaultExporters.Plain) - .AddJob(Job.ShortRun); - -BenchmarkDotNet.Running.BenchmarkRunner.Run(config, args); - -public struct MyVector3 -{ - public float X { get; set; } - - public float Y { get; set; } - - public float Z { get; set; } -} - -// var run = new DefaultRun(); -// await run.SetupAsync(); -// await run.RunBenchmark(); -// await run.RunStackExchangeRedis(); - -// await run.CleanupAsync(); -public class DefaultRun -{ -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - private NatsConnection _connection; - private string _subject; - private ConnectionMultiplexer _redis; - private object _gate; - private Handler _handler; - private IDisposable _subscription = default!; - - [GlobalSetup] - public async Task SetupAsync() - { - var provider = new ServiceCollection() - .AddLogging(x => - { - x.ClearProviders(); - x.SetMinimumLevel(LogLevel.Information); - x.AddZLoggerConsole(); - }) - .BuildServiceProvider(); - - var loggerFactory = provider.GetRequiredService(); - var logger = loggerFactory.CreateLogger>(); - var options = NatsOpts.Default with - { - LoggerFactory = loggerFactory, - Echo = true, - Verbose = false, - }; - - _connection = new NATS.Client.Core.NatsConnection(options); - _subject = "foobar"; - await _connection.ConnectAsync(); - _gate = new object(); - _redis = StackExchange.Redis.ConnectionMultiplexer.Connect("localhost"); - - _handler = new Handler(); - - // subscription = connection.Subscribe(key, handler.Handle); - } - - // [Benchmark] - public async Task Nop() - { - await Task.Yield(); - } - - [Benchmark] - public async Task PublishAsync() - { - for (var i = 0; i < 1; i++) - { - await _connection.PublishAsync(_subject, default(MyVector3)); - } - } - - // [Benchmark] - public async Task PublishAsyncRedis() - { - for (var i = 0; i < 1; i++) - { - await _redis.GetDatabase().PublishAsync(_subject, JsonSerializer.Serialize(default(MyVector3))); - } - } - - // [Benchmark] - public void RunBenchmark() - { - const int count = 10000; - _handler.Gate = _gate; - _handler.Called = 0; - _handler.Max = count; - - for (var i = 0; i < count; i++) - { - _connection.PublishAsync(_subject, default(MyVector3)); - } - - lock (_gate) - { - // Monitor.Wait(gate); - Thread.Sleep(1000); - } - } - - // [Benchmark] - // public async Task RunStackExchangeRedis() - // { - // var tcs = new TaskCompletionSource(); - // var called = 0; - // redis.GetSubscriber().Subscribe(key.Key, (channel, v) => - // { - // if (Interlocked.Increment(ref called) == 1000) - // { - // tcs.TrySetResult(); - // } - // }); - - // for (int i = 0; i < 1000; i++) - // { - // _ = redis.GetDatabase().PublishAsync(key.Key, JsonSerializer.Serialize(new MyVector3()), StackExchange.Redis.CommandFlags.FireAndForget); - // } - - // await tcs.Task; - // } - [GlobalCleanup] - public async Task CleanupAsync() - { - _subscription?.Dispose(); - if (_connection != null) - { - await _connection.DisposeAsync(); - } - - _redis?.Dispose(); - } - - private class Handler - { -#pragma warning disable SA1401 - public int Called; - public int Max; - public object Gate; -#pragma warning restore SA1401 - - public void Handle(MyVector3 vec) - { - if (Interlocked.Increment(ref Called) == Max) - { - lock (Gate) - { - Monitor.PulseAll(Gate); - } - } - } - } -} +BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); diff --git a/sandbox/MicroBenchmark/SerializationBuffersBench.cs b/sandbox/MicroBenchmark/SerializationBuffersBench.cs new file mode 100644 index 000000000..4bd864473 --- /dev/null +++ b/sandbox/MicroBenchmark/SerializationBuffersBench.cs @@ -0,0 +1,45 @@ +using BenchmarkDotNet.Attributes; +using NATS.Client.Core; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace MicroBenchmark; + +[MemoryDiagnoser] +[ShortRunJob] +[PlainExporter] +public class SerializationBuffersBench +{ + private static readonly string Data = new('0', 126); + private static readonly NatsPubOpts OptsWaitUntilSentTrue = new() { WaitUntilSent = true }; + private static readonly NatsPubOpts OptsWaitUntilSentFalse = new() { WaitUntilSent = false }; + + private NatsConnection _nats; + + [Params(64, 512, 1024)] + public int Iter { get; set; } + + [GlobalSetup] + public void Setup() => _nats = new NatsConnection(); + + [Benchmark] + public async ValueTask WaitUntilSentTrue() + { + for (var i = 0; i < Iter; i++) + { + await _nats.PublishAsync("foo", Data, opts: OptsWaitUntilSentTrue); + } + + return await _nats.PingAsync(); + } + + [Benchmark] + public async ValueTask WaitUntilSentFalse() + { + for (var i = 0; i < Iter; i++) + { + await _nats.PublishAsync("foo", Data, opts: OptsWaitUntilSentFalse); + } + + return await _nats.PingAsync(); + } +}