Reduce allocations for CreateDirectory (#61777)
authorAdam Sitnik <adam.sitnik@gmail.com>
Fri, 19 Nov 2021 12:02:26 +0000 (13:02 +0100)
committerGitHub <noreply@github.com>
Fri, 19 Nov 2021 12:02:26 +0000 (13:02 +0100)
* introduce an overload that accepts ROS<char> instead of a string

* remove dead code

* avoid string allocations

* remove List<int> allocation

* Apply suggestions from code review

Co-authored-by: Stephen Toub <stoub@microsoft.com>
src/libraries/Common/src/Interop/Unix/System.Native/Interop.MkDir.cs
src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj
src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs
src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs

index 499b1e7..09a4ae1 100644 (file)
@@ -3,12 +3,20 @@
 
 using System;
 using System.Runtime.InteropServices;
+using System.Text;
 
 internal static partial class Interop
 {
     internal static partial class Sys
     {
-        [GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MkDir", CharSet = CharSet.Ansi, SetLastError = true)]
-        internal static partial int MkDir(string path, int mode);
+        [GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MkDir", SetLastError = true)]
+        private static partial int MkDir(ref byte path, int mode);
+
+        internal static int MkDir(ReadOnlySpan<char> path, int mode)
+        {
+            using ValueUtf8Converter converter = new(stackalloc byte[DefaultPathBufferSize]);
+            int result = MkDir(ref MemoryMarshal.GetReference(converter.ConvertAndTerminateString(path)), mode);
+            return result;
+        }
     }
 }
index ae48db6..d597398 100644 (file)
              Link="Common\Interop\Unix\Interop.GetHostName.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetPeerUserName.cs"
              Link="Common\Interop\Unix\Interop.GetPeerUserName.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MkDir.cs"
-             Link="Common\Interop\Unix\Interop.MkDir.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Open.cs"
              Link="Common\Interop\Unix\Interop.Open.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.OpenFlags.cs"
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(LibrariesProjectRoot)System.Security.AccessControl\src\System.Security.AccessControl.csproj" />
-    <ProjectReference Include="$(LibrariesProjectRoot)System.Security.Principal.Windows\src\System.Security.Principal.Windows.csproj" />  
+    <ProjectReference Include="$(LibrariesProjectRoot)System.Security.Principal.Windows\src\System.Security.Principal.Windows.csproj" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="System.Collections" />
index 2fa979b..f4f0cac 100644 (file)
@@ -434,23 +434,6 @@ namespace System.IO.Pipes
             return LazyInitializer.EnsureInitialized(ref _asyncActiveSemaphore, () => new SemaphoreSlim(1, 1));
         }
 
-        private static void CreateDirectory(string directoryPath)
-        {
-            int result = Interop.Sys.MkDir(directoryPath, (int)Interop.Sys.Permissions.Mask);
-
-            // If successful created, we're done.
-            if (result >= 0)
-                return;
-
-            // If the directory already exists, consider it a success.
-            Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
-            if (errorInfo.Error == Interop.Error.EEXIST)
-                return;
-
-            // Otherwise, fail.
-            throw Interop.GetExceptionForIoErrno(errorInfo, directoryPath, isDirectory: true);
-        }
-
         /// <summary>Creates an anonymous pipe.</summary>
         /// <param name="reader">The resulting reader end of the pipe.</param>
         /// <param name="writer">The resulting writer end of the pipe.</param>
index e23ae9e..e5fd857 100644 (file)
@@ -298,9 +298,8 @@ namespace System.IO
             {
                 return; // Path already exists and it's a directory.
             }
-            else if (errorInfo.Error == Interop.Error.ENOENT)
+            else if (errorInfo.Error == Interop.Error.ENOENT) // Some parts of the path don't exist yet.
             {
-                // Some parts of the path don't exist yet.
                 CreateParentsAndDirectory(fullPath);
             }
             else
@@ -309,20 +308,19 @@ namespace System.IO
             }
         }
 
-        public static void CreateParentsAndDirectory(string fullPath)
+        private static void CreateParentsAndDirectory(string fullPath)
         {
             // Try create parents bottom to top and track those that could not
             // be created due to missing parents. Then create them top to bottom.
-            List<string> stackDir = new List<string>();
-
-            stackDir.Add(fullPath);
+            using ValueListBuilder<int> stackDir = new(stackalloc int[32]); // 32 arbitrarily chosen
+            stackDir.Append(fullPath.Length);
 
             int i = fullPath.Length - 1;
-            // Trim trailing separator.
             if (PathInternal.IsDirectorySeparator(fullPath[i]))
             {
-                i--;
+                i--; // Trim trailing separator.
             }
+
             do
             {
                 // Find the end of the parent directory.
@@ -332,13 +330,11 @@ namespace System.IO
                     i--;
                 }
 
-                // Try create it.
-                string mkdirPath = fullPath.Substring(0, i);
+                ReadOnlySpan<char> mkdirPath = fullPath.AsSpan(0, i);
                 int result = Interop.Sys.MkDir(mkdirPath, (int)Interop.Sys.Permissions.Mask);
                 if (result == 0)
                 {
-                    // Created parent.
-                    break;
+                    break; // Created parent.
                 }
 
                 Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
@@ -348,7 +344,7 @@ namespace System.IO
                     // We'll try to create its parent on the next iteration.
 
                     // Track this path for later creation.
-                    stackDir.Add(mkdirPath);
+                    stackDir.Append(mkdirPath.Length);
                 }
                 else if (errorInfo.Error == Interop.Error.EEXIST)
                 {
@@ -358,15 +354,15 @@ namespace System.IO
                 }
                 else
                 {
-                    throw Interop.GetExceptionForIoErrno(errorInfo, mkdirPath, isDirectory: true);
+                    throw Interop.GetExceptionForIoErrno(errorInfo, mkdirPath.ToString(), isDirectory: true);
                 }
                 i--;
             } while (i > 0);
 
             // Create directories that had missing parents.
-            for (i = stackDir.Count - 1; i >= 0; i--)
+            for (i = stackDir.Length - 1; i >= 0; i--)
             {
-                string mkdirPath = stackDir[i];
+                ReadOnlySpan<char> mkdirPath = fullPath.AsSpan(0, stackDir[i]);
                 int result = Interop.Sys.MkDir(mkdirPath, (int)Interop.Sys.Permissions.Mask);
                 if (result < 0)
                 {
@@ -386,7 +382,7 @@ namespace System.IO
                         }
                     }
 
-                    throw Interop.GetExceptionForIoErrno(errorInfo, mkdirPath, isDirectory: true);
+                    throw Interop.GetExceptionForIoErrno(errorInfo, mkdirPath.ToString(), isDirectory: true);
                 }
             }
         }