diff --git a/src/csharp/Grpc.Microbenchmarks/UnaryCallOverheadBenchmark.cs b/src/csharp/Grpc.Microbenchmarks/UnaryCallOverheadBenchmark.cs new file mode 100644 index 00000000000..a58a46695be --- /dev/null +++ b/src/csharp/Grpc.Microbenchmarks/UnaryCallOverheadBenchmark.cs @@ -0,0 +1,93 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Grpc.Core; +using Grpc.Core.Internal; +using System; + +namespace Grpc.Microbenchmarks +{ + // this test creates a real server and client, measuring the inherent inbuilt + // platform overheads; the marshallers **DO NOT ALLOCATE**, so any allocations + // are from the framework, not the messages themselves + + // important: allocs are not reliable on .NET Core until .NET Core 3, since + // this test involves multiple threads + + [ClrJob, CoreJob] // test .NET Core and .NET Framework + [MemoryDiagnoser] // allocations + public class UnaryCallOverheadBenchmark + { + private static readonly Task CompletedString = Task.FromResult(""); + private static readonly byte[] EmptyBlob = new byte[0]; + private static readonly Marshaller EmptyMarshaller = new Marshaller(_ => EmptyBlob, _ => ""); + private static readonly Method PingMethod = new Method(MethodType.Unary, nameof(PingBenchmark), "Ping", EmptyMarshaller, EmptyMarshaller); + + [Benchmark] + public string Ping() + { + return client.Ping("", new CallOptions()); + } + + Channel channel; + PingClient client; + + [GlobalSetup] + public void Setup() + { + // create client + channel = new Channel("localhost", 10042, ChannelCredentials.Insecure); + client = new PingClient(new DefaultCallInvoker(channel)); + + var native = NativeMethods.Get(); + + // replace the implementation of a native method with a fake + NativeMethods.Delegates.grpcsharp_call_start_unary_delegate fakeCallStartUnary = (CallSafeHandle call, BatchContextSafeHandle ctx, byte[] sendBuffer, UIntPtr sendBufferLen, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags metadataFlags) => { + return native.grpcsharp_test_call_start_unary_echo(call, ctx, sendBuffer, sendBufferLen, writeFlags, metadataArray, metadataFlags); + }; + native.GetType().GetField(nameof(native.grpcsharp_call_start_unary)).SetValue(native, fakeCallStartUnary); + + NativeMethods.Delegates.grpcsharp_completion_queue_pluck_delegate fakeCqPluck = (CompletionQueueSafeHandle cq, IntPtr tag) => { + return new CompletionQueueEvent { + type = CompletionQueueEvent.CompletionType.OpComplete, + success = 1, + tag = tag + }; + }; + native.GetType().GetField(nameof(native.grpcsharp_completion_queue_pluck)).SetValue(native, fakeCqPluck); + } + + [GlobalCleanup] + public async Task Cleanup() + { + await channel.ShutdownAsync(); + } + + class PingClient : LiteClientBase + { + public PingClient(CallInvoker callInvoker) : base(callInvoker) { } + + public string Ping(string request, CallOptions options) + { + return CallInvoker.BlockingUnaryCall(PingMethod, null, options, request); + } + } + } +}