diff --git a/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj b/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj
index 899e41ce532..f775e4c85fb 100644
--- a/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj
+++ b/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj
@@ -6,6 +6,7 @@
net461;netcoreapp2.1
Exe
true
+ true
diff --git a/src/csharp/Grpc.Microbenchmarks/Utf8Decode.cs b/src/csharp/Grpc.Microbenchmarks/Utf8Decode.cs
new file mode 100644
index 00000000000..1c3f4d261ee
--- /dev/null
+++ b/src/csharp/Grpc.Microbenchmarks/Utf8Decode.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using BenchmarkDotNet.Attributes;
+using Grpc.Core.Internal;
+
+namespace Grpc.Microbenchmarks
+{
+ [ClrJob, CoreJob] // test .NET Core and .NET Framework
+ [MemoryDiagnoser] // allocations
+ public class Utf8Decode
+ {
+ [Params(0, 1, 4, 128, 1024)]
+ public int PayloadSize { get; set; }
+
+ static readonly Dictionary Payloads = new Dictionary {
+ { 0, Invent(0) },
+ { 1, Invent(1) },
+ { 4, Invent(4) },
+ { 128, Invent(128) },
+ { 1024, Invent(1024) },
+ };
+
+ static byte[] Invent(int length)
+ {
+ var rand = new Random(Seed: length);
+ var chars = new char[length];
+ for(int i = 0; i < chars.Length; i++)
+ {
+ chars[i] = (char)rand.Next(32, 300);
+ }
+ return Encoding.UTF8.GetBytes(chars);
+ }
+
+ const int Iterations = 1000;
+ [Benchmark(OperationsPerInvoke = Iterations)]
+ public unsafe void Run()
+ {
+ byte[] payload = Payloads[PayloadSize];
+ fixed (byte* ptr = payload)
+ {
+ var iPtr = new IntPtr(ptr);
+ for (int i = 0; i < Iterations; i++)
+ {
+ MarshalUtils.PtrToStringUTF8(iPtr, payload.Length);
+ }
+ }
+ }
+ }
+}