Reduce allocations in UriHelper.StripBidiControlCharacters (#34794)
authorMiha Zupan <mihazupan.zupan1@gmail.com>
Sat, 11 Apr 2020 14:43:15 +0000 (16:43 +0200)
committerGitHub <noreply@github.com>
Sat, 11 Apr 2020 14:43:15 +0000 (16:43 +0200)
* Reduce allocations in UriHelper.StripBidiControlCharacters

* Use Span in StripBidiControlCharacters

* Span > char*

src/libraries/System.Private.Uri/src/System/DomainNameHelper.cs
src/libraries/System.Private.Uri/src/System/Uri.cs
src/libraries/System.Private.Uri/src/System/UriHelper.cs

index c8bcf23..64329e0 100644 (file)
@@ -230,14 +230,7 @@ namespace System
                 return hostname.ToLowerInvariant();
             }
 
-            string bidiStrippedHost;
-            unsafe
-            {
-                fixed (char* hostnamePtr = hostname)
-                {
-                    bidiStrippedHost = UriHelper.StripBidiControlCharacter(hostnamePtr, 0, hostname.Length);
-                }
-            }
+            string bidiStrippedHost = UriHelper.StripBidiControlCharacters(hostname, hostname);
 
             try
             {
@@ -306,7 +299,7 @@ namespace System
             if (end <= start)
                 return idn;
 
-            string unescapedHostname = UriHelper.StripBidiControlCharacter(hostname, start, (end - start));
+            string unescapedHostname = UriHelper.StripBidiControlCharacters(new ReadOnlySpan<char>(hostname + start, end - start));
 
             string? unicodeEqvlHost = null;
             int curPos = 0;
index c49ef8f..7924005 100644 (file)
@@ -4329,10 +4329,10 @@ namespace System
 
             if (hasUnicode)
             {
-                string temp = UriHelper.StripBidiControlCharacter(pString, start, end - start);
+                string temp = UriHelper.StripBidiControlCharacters(new ReadOnlySpan<char>(pString + start, end - start));
                 try
                 {
-                    newHost += ((temp != null) ? temp.Normalize(NormalizationForm.FormC) : null);
+                    newHost += temp.Normalize(NormalizationForm.FormC);
                 }
                 catch (ArgumentException)
                 {
index 3e354bd..0f8194d 100644 (file)
@@ -5,6 +5,7 @@
 using System.Text;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
 
 namespace System
 {
@@ -792,21 +793,45 @@ namespace System
         //
         // Strip Bidirectional control characters from this string
         //
-        internal static unsafe string StripBidiControlCharacter(char* strToClean, int start, int length)
+        internal static unsafe string StripBidiControlCharacters(ReadOnlySpan<char> strToClean, string? backingString = null)
         {
-            if (length <= 0) return "";
+            Debug.Assert(backingString is null || strToClean.Length == backingString.Length);
 
-            char[] cleanStr = new char[length];
-            int count = 0;
-            for (int i = 0; i < length; ++i)
+            int charsToRemove = 0;
+            foreach (char c in strToClean)
             {
-                char c = strToClean[start + i];
-                if (c < '\u200E' || c > '\u202E' || !IsBidiControlCharacter(c))
+                if ((uint)(c - '\u200E') <= ('\u202E' - '\u200E') && IsBidiControlCharacter(c))
                 {
-                    cleanStr[count++] = c;
+                    charsToRemove++;
                 }
             }
-            return new string(cleanStr, 0, count);
+
+            if (charsToRemove == 0)
+            {
+                return backingString ?? new string(strToClean);
+            }
+
+            if (charsToRemove == strToClean.Length)
+            {
+                return string.Empty;
+            }
+
+            fixed (char* pStrToClean = &MemoryMarshal.GetReference(strToClean))
+            {
+                return string.Create(strToClean.Length - charsToRemove, (StrToClean: (IntPtr)pStrToClean, strToClean.Length), (buffer, state) =>
+                {
+                    var strToClean = new ReadOnlySpan<char>((char*)state.StrToClean, state.Length);
+                    int destIndex = 0;
+                    foreach (char c in strToClean)
+                    {
+                        if ((uint)(c - '\u200E') > ('\u202E' - '\u200E') || !IsBidiControlCharacter(c))
+                        {
+                            buffer[destIndex++] = c;
+                        }
+                    }
+                    Debug.Assert(buffer.Length == destIndex);
+                });
+            }
         }
     }
 }