mirror of https://github.com/grpc/grpc.git
Merge pull request #19511 from mgravell/mgravell/unsafe-encode
csharp remove byte[] allocations during UTF8 encode/decodepull/19568/head
commit
70dd0b9b3b
15 changed files with 332 additions and 48 deletions
@ -0,0 +1,54 @@ |
||||
#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; |
||||
using System.Runtime.CompilerServices; |
||||
using System.Text; |
||||
|
||||
namespace Grpc.Core.Api.Utils |
||||
{ |
||||
|
||||
internal static class EncodingExtensions |
||||
{ |
||||
#if NET45 // back-fill over a method missing in NET45 |
||||
/// <summary> |
||||
/// Converts <c>byte*</c> pointing to an encoded byte array to a <c>string</c> using the provided <c>Encoding</c>. |
||||
/// </summary> |
||||
public static unsafe string GetString(this Encoding encoding, byte* source, int byteCount) |
||||
{ |
||||
if (byteCount == 0) return ""; // most callers will have already checked, but: make sure |
||||
|
||||
// allocate a right-sized string and decode into it |
||||
int charCount = encoding.GetCharCount(source, byteCount); |
||||
string s = new string('\0', charCount); |
||||
fixed (char* cPtr = s) |
||||
{ |
||||
encoding.GetChars(source, byteCount, cPtr, charCount); |
||||
} |
||||
return s; |
||||
} |
||||
#endif |
||||
/// <summary> |
||||
/// Converts <c>IntPtr</c> pointing to a encoded byte array to a <c>string</c> using the provided <c>Encoding</c>. |
||||
/// </summary> |
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
public static unsafe string GetString(this Encoding encoding, IntPtr ptr, int len) |
||||
{ |
||||
return len == 0 ? "" : encoding.GetString((byte*)ptr.ToPointer(), len); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,62 @@ |
||||
#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.Text; |
||||
using Grpc.Core.Internal; |
||||
using NUnit.Framework; |
||||
|
||||
namespace Grpc.Core.Internal.Tests |
||||
{ |
||||
public class WellKnownStringsTest |
||||
{ |
||||
[Test] |
||||
[TestCase("", true)] |
||||
[TestCase("u", false)] |
||||
[TestCase("us", false)] |
||||
[TestCase("use", false)] |
||||
[TestCase("user", false)] |
||||
[TestCase("user-", false)] |
||||
[TestCase("user-a", false)] |
||||
[TestCase("user-ag", false)] |
||||
[TestCase("user-age", false)] |
||||
[TestCase("user-agent", true)] |
||||
[TestCase("user-agent ", false)] |
||||
[TestCase("useragent ", false)] |
||||
[TestCase("User-Agent", false)] |
||||
[TestCase("sdlkfjlskjfdlkjs;lfdksflsdfkh skjdfh sdkfhskdhf skjfhk sdhjkjh", false)] |
||||
|
||||
// test for endianness snafus (reversed in segments) |
||||
[TestCase("ega-resutn", false)] |
||||
public unsafe void TestWellKnownStrings(string input, bool expected) |
||||
{ |
||||
// create a copy of the data; no cheating! |
||||
byte[] bytes = Encoding.ASCII.GetBytes(input); |
||||
fixed(byte* ptr = bytes) |
||||
{ |
||||
string result = WellKnownStrings.TryIdentify(ptr, bytes.Length); |
||||
if (expected) Assert.AreEqual(input, result); |
||||
else Assert.IsNull(result); |
||||
|
||||
if (expected) |
||||
{ |
||||
// try again, and check we get the same instance |
||||
string again = WellKnownStrings.TryIdentify(ptr, bytes.Length); |
||||
Assert.AreSame(result, again); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,92 @@ |
||||
#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; |
||||
using System.Runtime.CompilerServices; |
||||
|
||||
namespace Grpc.Core.Internal |
||||
{ |
||||
/// <summary> |
||||
/// Utility type for identifying "well-known" strings (i.e. headers/keys etc that |
||||
/// we expect to see frequently, and don't want to allocate lots of copies of) |
||||
/// </summary> |
||||
internal static class WellKnownStrings |
||||
{ |
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
private static unsafe ulong Coerce64(byte* value) |
||||
{ |
||||
return *(ulong*)value; |
||||
} |
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
private static unsafe uint Coerce32(byte* value) |
||||
{ |
||||
return *(uint*)value; |
||||
} |
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
private static unsafe ushort Coerce16(byte* value) |
||||
{ |
||||
return *(ushort*)value; |
||||
} |
||||
|
||||
|
||||
/// <summary> |
||||
/// Test whether the provided byte sequence is recognized as a well-known string; if |
||||
/// so, return a shared instance of that string; otherwise, return null |
||||
/// </summary> |
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
public static unsafe string TryIdentify(IntPtr source, int length) |
||||
{ |
||||
return TryIdentify((byte*)source.ToPointer(), length); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Test whether the provided byte sequence is recognized as a well-known string; if |
||||
/// so, return a shared instance of that string; otherwise, return null |
||||
/// </summary> |
||||
public static unsafe string TryIdentify(byte* source, int length) |
||||
{ |
||||
// note: the logic here is hard-coded to constants for optimal processing; |
||||
// refer to an ASCII/hex converter (and remember to reverse **segments** for little-endian) |
||||
if (BitConverter.IsLittleEndian) // this is a JIT intrinsic; branch removal happens on modern runtimes |
||||
{ |
||||
switch (length) |
||||
{ |
||||
case 0: return ""; |
||||
case 10: |
||||
switch(Coerce64(source)) |
||||
{ |
||||
case 0x6567612d72657375: return Coerce16(source + 8) == 0x746e ? "user-agent" : null; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
switch (length) |
||||
{ |
||||
case 0: return ""; |
||||
case 10: |
||||
switch (Coerce64(source)) |
||||
{ |
||||
case 0x757365722d616765: return Coerce16(source + 8) == 0x6e74 ? "user-agent" : null; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue