Avoid unnecessary string allocations in IdnMapping (#17399)
authorStephen Toub <stoub@microsoft.com>
Wed, 4 Apr 2018 10:53:46 +0000 (03:53 -0700)
committerGitHub <noreply@github.com>
Wed, 4 Apr 2018 10:53:46 +0000 (03:53 -0700)
If the output matches the input string, we can just use the input string as the result.

src/mscorlib/shared/System/Globalization/IdnMapping.Unix.cs
src/mscorlib/shared/System/Globalization/IdnMapping.Windows.cs
src/mscorlib/shared/System/Globalization/IdnMapping.cs

index 5320936..20f753e 100644 (file)
@@ -8,9 +8,10 @@ namespace System.Globalization
 {
     sealed partial class IdnMapping
     {
-        private unsafe string GetAsciiCore(char* unicode, int count)
+        private unsafe string GetAsciiCore(string unicodeString, char* unicode, int count)
         {
             Debug.Assert(!GlobalizationMode.Invariant);
+            Debug.Assert(unicodeString != null && unicodeString.Length >= count);
 
             uint flags = Flags;
             CheckInvalidIdnCharacters(unicode, count, flags, nameof(unicode));
@@ -26,7 +27,7 @@ namespace System.Globalization
                 actualLength = Interop.Globalization.ToAscii(flags, unicode, count, outputStack, estimatedLength);
                 if (actualLength > 0 && actualLength <= estimatedLength)
                 {
-                    return new string(outputStack, 0, actualLength);
+                    return GetStringForOutput(unicodeString, unicode, count, outputStack, actualLength);
                 }
             }
             else
@@ -46,13 +47,14 @@ namespace System.Globalization
                 {
                     throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode));
                 }
-                return new string(pOutputHeap, 0, actualLength);
+                return GetStringForOutput(unicodeString, unicode, count, pOutputHeap, actualLength);
             }
         }
 
-        private unsafe string GetUnicodeCore(char* ascii, int count)
+        private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count)
         {
             Debug.Assert(!GlobalizationMode.Invariant);
+            Debug.Assert(asciiString != null && asciiString.Length >= count);
 
             uint flags = Flags;
             CheckInvalidIdnCharacters(ascii, count, flags, nameof(ascii));
@@ -61,21 +63,22 @@ namespace System.Globalization
             if (count < StackAllocThreshold)
             {
                 char* output = stackalloc char[count];
-                return GetUnicodeCore(ascii, count, flags, output, count, reattempt: true);
+                return GetUnicodeCore(asciiString, ascii, count, flags, output, count, reattempt: true);
             }
             else
             {
                 char[] output = new char[count];
                 fixed (char* pOutput = &output[0])
                 {
-                    return GetUnicodeCore(ascii, count, flags, pOutput, count, reattempt: true);
+                    return GetUnicodeCore(asciiString, ascii, count, flags, pOutput, count, reattempt: true);
                 }
             }
         }
 
-        private unsafe string GetUnicodeCore(char* ascii, int count, uint flags, char* output, int outputLength, bool reattempt)
+        private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count, uint flags, char* output, int outputLength, bool reattempt)
         {
             Debug.Assert(!GlobalizationMode.Invariant);
+            Debug.Assert(asciiString != null && asciiString.Length >= count);
 
             int realLen = Interop.Globalization.ToUnicode(flags, ascii, count, output, outputLength);
 
@@ -85,14 +88,14 @@ namespace System.Globalization
             }
             else if (realLen <= outputLength)
             {
-                return new string(output, 0, realLen);
+                return GetStringForOutput(asciiString, ascii, count, output, realLen);
             }
             else if (reattempt)
             {
                 char[] newOutput = new char[realLen];
                 fixed (char* pNewOutput = newOutput)
                 {
-                    return GetUnicodeCore(ascii, count, flags, pNewOutput, realLen, reattempt: false);
+                    return GetUnicodeCore(asciiString, ascii, count, flags, pNewOutput, realLen, reattempt: false);
                 }
             }
 
index 35da734..9d491df 100644 (file)
@@ -9,9 +9,10 @@ namespace System.Globalization
 {
     public sealed partial class IdnMapping
     {
-        private unsafe string GetAsciiCore(char* unicode, int count)
+        private unsafe string GetAsciiCore(string unicodeString, char* unicode, int count)
         {
             Debug.Assert(!GlobalizationMode.Invariant);
+            Debug.Assert(unicodeString != null && unicodeString.Length >= count);
 
             uint flags = Flags;
 
@@ -27,21 +28,22 @@ namespace System.Globalization
             if (length < StackAllocThreshold)
             {
                 char* output = stackalloc char[length];
-                return GetAsciiCore(unicode, count, flags, output, length);
+                return GetAsciiCore(unicodeString, unicode, count, flags, output, length);
             }
             else
             {
                 char[] output = new char[length];
                 fixed (char* pOutput = &output[0])
                 {
-                    return GetAsciiCore(unicode, count, flags, pOutput, length);
+                    return GetAsciiCore(unicodeString, unicode, count, flags, pOutput, length);
                 }
             }
         }
 
-        private unsafe string GetAsciiCore(char* unicode, int count, uint flags, char* output, int outputLength)
+        private unsafe string GetAsciiCore(string unicodeString, char* unicode, int count, uint flags, char* output, int outputLength)
         {
             Debug.Assert(!GlobalizationMode.Invariant);
+            Debug.Assert(unicodeString != null && unicodeString.Length >= count);
 
             int length = Interop.Normaliz.IdnToAscii(flags, unicode, count, output, outputLength);
             if (length == 0)
@@ -49,12 +51,13 @@ namespace System.Globalization
                 ThrowForZeroLength(unicode: true);
             }
             Debug.Assert(length == outputLength);
-            return new string(output, 0, length);
+            return GetStringForOutput(unicodeString, unicode, count, output, length);
         }
 
-        private unsafe string GetUnicodeCore(char* ascii, int count)
+        private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count)
         {
             Debug.Assert(!GlobalizationMode.Invariant);
+            Debug.Assert(asciiString != null && asciiString.Length >= count);
 
             uint flags = Flags;
 
@@ -70,21 +73,22 @@ namespace System.Globalization
             if (length < StackAllocThreshold)
             {
                 char* output = stackalloc char[length];
-                return GetUnicodeCore(ascii, count, flags, output, length);
+                return GetUnicodeCore(asciiString, ascii, count, flags, output, length);
             }
             else
             {
                 char[] output = new char[length];
                 fixed (char* pOutput = &output[0])
                 {
-                    return GetUnicodeCore(ascii, count, flags, pOutput, length);
+                    return GetUnicodeCore(asciiString, ascii, count, flags, pOutput, length);
                 }
             }
         }
 
-        private unsafe string GetUnicodeCore(char* ascii, int count, uint flags, char* output, int outputLength)
+        private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count, uint flags, char* output, int outputLength)
         {
             Debug.Assert(!GlobalizationMode.Invariant);
+            Debug.Assert(asciiString != null && asciiString.Length >= count);
 
             int length = Interop.Normaliz.IdnToUnicode(flags, ascii, count, output, outputLength);
             if (length == 0)
@@ -92,7 +96,7 @@ namespace System.Globalization
                 ThrowForZeroLength(unicode: false);
             }
             Debug.Assert(length == outputLength);
-            return new string(output, 0, length);
+            return GetStringForOutput(asciiString, ascii, count, output, length);
         }
 
         // -----------------------------
index 176e5fe..6da6f79 100644 (file)
@@ -25,6 +25,7 @@
 //  RFC 3492 - Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)
 
 using System.Diagnostics;
+using System.Runtime.CompilerServices;
 using System.Text;
 
 namespace System.Globalization
@@ -93,7 +94,7 @@ namespace System.Globalization
             {
                 fixed (char* pUnicode = unicode)
                 {
-                    return GetAsciiCore(pUnicode + index, count);
+                    return GetAsciiCore(unicode, pUnicode + index, count);
                 }
             }
         }
@@ -137,7 +138,7 @@ namespace System.Globalization
             {
                 fixed (char* pAscii = ascii)
                 {
-                    return GetUnicodeCore(pAscii + index, count);
+                    return GetUnicodeCore(ascii, pAscii + index, count);
                 }
             }
         }
@@ -156,6 +157,14 @@ namespace System.Globalization
             return (_allowUnassigned ? 100 : 200) + (_useStd3AsciiRules ? 1000 : 2000);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static unsafe string GetStringForOutput(string originalString, char* input, int inputLength, char* output, int outputLength)
+        {
+            return originalString.Length == inputLength && new ReadOnlySpan<char>(input, inputLength).SequenceEqual(new ReadOnlySpan<char>(output, outputLength)) ?
+                originalString :
+                new string(output, 0, outputLength);
+        }
+
         //
         // Invariant implementation
         //