Skip to content

Instantly share code, notes, and snippets.

@mgravell
Created January 19, 2023 14:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mgravell/1a4a45bf0b0918ae75d14f1e3a4a06cd to your computer and use it in GitHub Desktop.
Save mgravell/1a4a45bf0b0918ae75d14f1e3a4a06cd to your computer and use it in GitHub Desktop.
bench.md
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Grpc.Core;
using ProtoBuf.Grpc.Client;
using ProtoBuf.Grpc.Configuration;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
BenchmarkRunner.Run<MyBenchmark>();
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net70)]
public class MyBenchmark
{
private readonly ChannelBase _grpcChannel = new DummyChannel();
private readonly ConcurrentDictionary<Type, object> _channelCache = new();
[Benchmark]
public IService WithCache() => GetGrpcServiceWithCache<IService>();
[Benchmark(Baseline = true)]
public IService WithoutCache() => _grpcChannel.CreateGrpcService<IService>();
[Benchmark]
public IService WithCache2() => GetGrpcServiceWithCache2<IService>();
private T GetGrpcServiceWithCache<T>() where T : class
{
if (_channelCache.TryGetValue(typeof(T), out var obj))
return (T)obj;
var service = _grpcChannel.CreateGrpcService<T>();
if (!_channelCache.TryAdd(typeof(T), service))
service = (T)_channelCache[typeof(T)];
return service;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T GetGrpcServiceWithCache2<T>() where T : class
=> _channelCache.TryGetValue(typeof(T), out var obj) ? (T)obj : SlowGet<T>();
[MethodImpl(MethodImplOptions.NoInlining)]
private T SlowGet<T>() where T : class
{
var service = _grpcChannel.CreateGrpcService<T>();
if (!_channelCache.TryAdd(typeof(T), service))
service = (T)_channelCache[typeof(T)];
return service;
}
}
[Service]
public interface IService { }
sealed class DummyChannel : ChannelBase
{
public DummyChannel() : base("") { }
public override CallInvoker CreateCallInvoker()
=> DummyCallInvoker.Instance;
}
sealed class DummyCallInvoker : CallInvoker
{
public static DummyCallInvoker Instance { get; } = new();
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options) => throw new NotSupportedException();
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options) => throw new NotSupportedException();
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options, TRequest request) => throw new NotSupportedException();
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options, TRequest request) => throw new NotSupportedException();
public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options, TRequest request) => throw new NotSupportedException();
}
```
BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
Intel Core i9-9900K CPU 3.60GHz (Coffee Lake), 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job=.NET 7.0 Runtime=.NET 7.0
```
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|------------- |---------:|---------:|---------:|------:|-------:|----------:|------------:|
| WithCache | 14.41 ns | 0.205 ns | 0.182 ns | 0.31 | - | - | 0.00 |
| WithoutCache | 46.19 ns | 0.955 ns | 1.100 ns | 1.00 | 0.0220 | 184 B | 1.00 |
| WithCache2 | 13.24 ns | 0.092 ns | 0.082 ns | 0.29 | - | - | 0.00 |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment