diff --git a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs index f4aea685c31..b68655b33ca 100644 --- a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs +++ b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs @@ -19,7 +19,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading; using Grpc.Core.Logging; using Grpc.Core.Utils; @@ -35,7 +37,7 @@ namespace Grpc.Core.Internal readonly GrpcEnvironment environment; readonly Dictionary dict = new Dictionary(new IntPtrComparer()); - readonly object myLock = new object(); + SpinLock spinLock = new SpinLock(Debugger.IsAttached); IntPtr lastRegisteredKey; // only for testing public CompletionRegistry(GrpcEnvironment environment) @@ -46,11 +48,19 @@ namespace Grpc.Core.Internal public void Register(IntPtr key, IOpCompletionCallback callback) { environment.DebugStats.PendingBatchCompletions.Increment(); - lock (myLock) + + bool lockTaken = false; + try { + spinLock.Enter(ref lockTaken); + dict.Add(key, callback); this.lastRegisteredKey = key; } + finally + { + if (lockTaken) spinLock.Exit(); + } } public void RegisterBatchCompletion(BatchContextSafeHandle ctx, BatchCompletionDelegate callback, object state) @@ -68,11 +78,18 @@ namespace Grpc.Core.Internal public IOpCompletionCallback Extract(IntPtr key) { IOpCompletionCallback value = null; - lock (myLock) + bool lockTaken = false; + try { + spinLock.Enter(ref lockTaken); + value = dict[key]; dict.Remove(key); } + finally + { + if (lockTaken) spinLock.Exit(); + } environment.DebugStats.PendingBatchCompletions.Decrement(); return value; } diff --git a/src/csharp/Grpc.Microbenchmarks/CompletionRegistryBenchmark.cs b/src/csharp/Grpc.Microbenchmarks/CompletionRegistryBenchmark.cs index 0e2fb070f19..2d1c33e9a0c 100644 --- a/src/csharp/Grpc.Microbenchmarks/CompletionRegistryBenchmark.cs +++ b/src/csharp/Grpc.Microbenchmarks/CompletionRegistryBenchmark.cs @@ -40,24 +40,25 @@ namespace Grpc.Microbenchmarks GrpcEnvironment.ReleaseAsync().Wait(); } - public void Run(int threadCount, int iterations) + public void Run(int threadCount, int iterations, bool useSharedRegistry) { - Console.WriteLine(string.Format("CompletionRegistryBenchmark: threads={0}, iterations={1}", threadCount, iterations)); - var threadedBenchmark = new ThreadedBenchmark(threadCount, () => ThreadBody(iterations)); + Console.WriteLine(string.Format("CompletionRegistryBenchmark: threads={0}, iterations={1}, useSharedRegistry={2}", threadCount, iterations, useSharedRegistry)); + CompletionRegistry sharedRegistry = useSharedRegistry ? new CompletionRegistry(environment) : null; + var threadedBenchmark = new ThreadedBenchmark(threadCount, () => ThreadBody(iterations, sharedRegistry)); threadedBenchmark.Run(); // TODO: parametrize by number of pending completions } - private void ThreadBody(int iterations) + private void ThreadBody(int iterations, CompletionRegistry optionalSharedRegistry) { - var completionRegistry = new CompletionRegistry(environment); + var completionRegistry = optionalSharedRegistry ?? new CompletionRegistry(environment); var ctx = BatchContextSafeHandle.Create(); var stopwatch = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { completionRegistry.Register(ctx.Handle, ctx); - var callback = completionRegistry.Extract(completionRegistry.LastRegisteredKey); + var callback = completionRegistry.Extract(ctx.Handle); // NOTE: we are not calling the callback to avoid disposing ctx. } stopwatch.Stop(); diff --git a/src/csharp/Grpc.Microbenchmarks/Program.cs b/src/csharp/Grpc.Microbenchmarks/Program.cs index 79ec308501d..a64c2979abe 100644 --- a/src/csharp/Grpc.Microbenchmarks/Program.cs +++ b/src/csharp/Grpc.Microbenchmarks/Program.cs @@ -77,7 +77,10 @@ namespace Grpc.Microbenchmarks benchmark.Init(); foreach (int threadCount in new int[] {1, 1, 2, 4, 8, 12}) { - benchmark.Run(threadCount, 4 * 1000 * 1000); + foreach (bool useSharedRegistry in new bool[] {false, true}) + { + benchmark.Run(threadCount, 4 * 1000 * 1000, useSharedRegistry); + } } benchmark.Cleanup(); }