Skip to content

Commit

Permalink
Removing unsafe parts of Decode method in QueryStringEnumerable (#56630)
Browse files Browse the repository at this point in the history
* Using TryUnescapeDataString to avoid extra string allocation

---------

Co-authored-by: ladeak <ladeak87@windowslive.com>
  • Loading branch information
ladeak and ladeak authored Jul 9, 2024
1 parent 96f53b4 commit c52c284
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 24 deletions.
17 changes: 4 additions & 13 deletions src/Http/Http/perf/Microbenchmarks/QueryCollectionBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,10 @@ namespace Microsoft.AspNetCore.Http;
[CategoriesColumn]
public class QueryCollectionBenchmarks
{
private string _queryString;
private string _singleValue;
private string _singleValueWithPlus;
private string _encoded;

[IterationSetup]
public void Setup()
{
_queryString = "?key1=value1&key2=value2&key3=value3&key4=&key5=";
_singleValue = "?key1=value1";
_singleValueWithPlus = "?key1=value1+value2+value3";
_encoded = "?key1=value%231";
}
private const string _queryString = "?key1=value1&key2=value2&key3=value3&key4=&key5=";
private const string _singleValue = "?key1=value1";
private const string _singleValueWithPlus = "?key1=value1+value2+value3";
private const string _encoded = "?key1=value%231";

[Benchmark(Description = "ParseNew")]
[BenchmarkCategory("QueryString")]
Expand Down
20 changes: 9 additions & 11 deletions src/Shared/QueryStringEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
Expand Down Expand Up @@ -88,21 +89,18 @@ public ReadOnlyMemory<char> DecodeName()
public ReadOnlyMemory<char> DecodeValue()
=> Decode(EncodedValue);

private static unsafe ReadOnlyMemory<char> Decode(ReadOnlyMemory<char> chars)
private static ReadOnlyMemory<char> Decode(ReadOnlyMemory<char> chars)
{
// If the value is short, it's cheap to check up front if it really needs decoding. If it doesn't,
// then we can save some allocations.
if (chars.Length < 16 && chars.Span.IndexOfAny('%', '+') < 0)
ReadOnlySpan<char> source = chars.Span;
if (!source.ContainsAny('%', '+'))
{
return chars;
}

#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
ReadOnlySpan<char> span = chars.Span;
return Uri.UnescapeDataString(
string.Create(span.Length,
(IntPtr)(&span), static (dest, ptr) => ((ReadOnlySpan<char>*)ptr)->Replace(dest, '+', ' '))).AsMemory();
#pragma warning restore CS8500
var buffer = new char[source.Length];
source.Replace(buffer, '+', ' ');
var success = Uri.TryUnescapeDataString(buffer, buffer, out var unescapedLength);
Debug.Assert(success);
return buffer.AsMemory(0, unescapedLength);
}
}

Expand Down

0 comments on commit c52c284

Please sign in to comment.