Slightly increase throughput of string.Concat(object[]) (dotnet/coreclr#6547)
authorJames Ko <jamesqko@gmail.com>
Tue, 2 Aug 2016 07:02:41 +0000 (03:02 -0400)
committerJan Kotas <jkotas@microsoft.com>
Tue, 2 Aug 2016 07:02:41 +0000 (00:02 -0700)
Commit migrated from https://github.com/dotnet/coreclr/commit/6773ee18180157be465f11cefc706b46f9d6c5dc

src/coreclr/src/mscorlib/mscorlib.shared.sources.props
src/coreclr/src/mscorlib/src/System/String.cs

index eed8917..8721252 100644 (file)
     <SystemSources Condition="'$(FeatureClassicCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\OleAutBinder.cs" />
   </ItemGroup>
   <ItemGroup>
-    <SystemSources Condition="'$(FeatureCoreclr)' == 'true'" Include="$(BclSourcesRoot)\Internal\Runtime\Augments\EnvironmentAugments.cs" />
+    <InternalSources Condition="'$(FeatureCoreclr)' == 'true'" Include="$(BclSourcesRoot)\Internal\Runtime\Augments\EnvironmentAugments.cs" />
   </ItemGroup>
   <ItemGroup>
     <ReflectionSources Include="$(BclSourcesRoot)\System\Reflection\__Filters.cs" />
     <MscorlibSources Include="@(IdentitySources)"/>
     <MscorlibSources Include="@(VersioningSources)"/>
     <MscorlibSources Include="@(DesignerServicesSources)"/>
+    <MscorlibSources Include="@(InternalSources)"/>
     <MscorlibSources Include="$(BclSourcesRoot)\GlobalSuppressions.cs"/>
   </ItemGroup>
 </Project>
index aebc2e5..df39181 100644 (file)
@@ -3350,34 +3350,61 @@ namespace System {
         }
 
         [System.Security.SecuritySafeCritical]
-        public static String Concat(params Object[] args) {
-            if (args==null) {
+        public static string Concat(params object[] args)
+        {
+            if (args == null)
+            {
                 throw new ArgumentNullException("args");
             }
             Contract.Ensures(Contract.Result<String>() != null);
             Contract.EndContractBlock();
-    
-            String[] sArgs = new String[args.Length];
-            int totalLength=0;
+
+            // We need to get an intermediary string array
+            // to fill with each of the args' ToString(),
+            // and then just concat that in one operation.
+
+            // This way we avoid any intermediary string representations,
+            // or buffer resizing if we use StringBuilder (although the
+            // latter case is partially alleviated due to StringBuilder's
+            // linked-list style implementation)
+
+            var strings = new string[args.Length];
             
-            for (int i=0; i<args.Length; i++) {
+            int totalLength = 0;
+
+            for (int i = 0; i < args.Length; i++)
+            {
                 object value = args[i];
-                sArgs[i] = ((value==null)?(String.Empty):(value.ToString()));
-                if (sArgs[i] == null) sArgs[i] = String.Empty; // value.ToString() above could have returned null
-                totalLength += sArgs[i].Length;
-                // check for overflow
-                if (totalLength < 0) {
+
+                string toString = value?.ToString() ?? string.Empty; // We need to handle both the cases when value or value.ToString() is null
+                strings[i] = toString;
+
+                totalLength += toString.Length;
+
+                if (totalLength < 0) // Check for a positive overflow
+                {
                     throw new OutOfMemoryException();
                 }
             }
 
+            // If all of the ToStrings are null/empty, just return string.Empty
+            if (totalLength == 0)
+            {
+                return string.Empty;
+            }
+
             string result = FastAllocateString(totalLength);
-            int currPos = 0;
-            for (int i = 0; i < sArgs.Length; i++)
+            int position = 0; // How many characters we've copied so far
+
+            for (int i = 0; i < strings.Length; i++)
             {
-                Contract.Assert(currPos <= totalLength - sArgs[i].Length, "[String.Concat](currPos <= totalLength - sArgs[i].Length)");
-                FillStringChecked(result, currPos, sArgs[i]);
-                currPos += sArgs[i].Length;
+                string s = strings[i];
+
+                Contract.Assert(s != null);
+                Contract.Assert(position <= totalLength - s.Length, "We didn't allocate enough space for the result string!");
+
+                FillStringChecked(result, position, s);
+                position += s.Length;
             }
 
             return result;