Skip to content

Commit ba4eae0

Browse files
Reduce allocations for CreateDirectory (#61777)
* 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>
1 parent b4d16b7 commit ba4eae0

File tree

4 files changed

+24
-39
lines changed

4 files changed

+24
-39
lines changed

src/libraries/Common/src/Interop/Unix/System.Native/Interop.MkDir.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@
33

44
using System;
55
using System.Runtime.InteropServices;
6+
using System.Text;
67

78
internal static partial class Interop
89
{
910
internal static partial class Sys
1011
{
11-
[GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MkDir", CharSet = CharSet.Ansi, SetLastError = true)]
12-
internal static partial int MkDir(string path, int mode);
12+
[GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MkDir", SetLastError = true)]
13+
private static partial int MkDir(ref byte path, int mode);
14+
15+
internal static int MkDir(ReadOnlySpan<char> path, int mode)
16+
{
17+
using ValueUtf8Converter converter = new(stackalloc byte[DefaultPathBufferSize]);
18+
int result = MkDir(ref MemoryMarshal.GetReference(converter.ConvertAndTerminateString(path)), mode);
19+
return result;
20+
}
1321
}
1422
}

src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,6 @@
137137
Link="Common\Interop\Unix\Interop.GetHostName.cs" />
138138
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetPeerUserName.cs"
139139
Link="Common\Interop\Unix\Interop.GetPeerUserName.cs" />
140-
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MkDir.cs"
141-
Link="Common\Interop\Unix\Interop.MkDir.cs" />
142140
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Open.cs"
143141
Link="Common\Interop\Unix\Interop.Open.cs" />
144142
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.OpenFlags.cs"
@@ -166,7 +164,7 @@
166164
</ItemGroup>
167165
<ItemGroup>
168166
<ProjectReference Include="$(LibrariesProjectRoot)System.Security.AccessControl\src\System.Security.AccessControl.csproj" />
169-
<ProjectReference Include="$(LibrariesProjectRoot)System.Security.Principal.Windows\src\System.Security.Principal.Windows.csproj" />
167+
<ProjectReference Include="$(LibrariesProjectRoot)System.Security.Principal.Windows\src\System.Security.Principal.Windows.csproj" />
170168
</ItemGroup>
171169
<ItemGroup>
172170
<Reference Include="System.Collections" />

src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -434,23 +434,6 @@ private SemaphoreSlim EnsureAsyncActiveSemaphoreInitialized()
434434
return LazyInitializer.EnsureInitialized(ref _asyncActiveSemaphore, () => new SemaphoreSlim(1, 1));
435435
}
436436

437-
private static void CreateDirectory(string directoryPath)
438-
{
439-
int result = Interop.Sys.MkDir(directoryPath, (int)Interop.Sys.Permissions.Mask);
440-
441-
// If successful created, we're done.
442-
if (result >= 0)
443-
return;
444-
445-
// If the directory already exists, consider it a success.
446-
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
447-
if (errorInfo.Error == Interop.Error.EEXIST)
448-
return;
449-
450-
// Otherwise, fail.
451-
throw Interop.GetExceptionForIoErrno(errorInfo, directoryPath, isDirectory: true);
452-
}
453-
454437
/// <summary>Creates an anonymous pipe.</summary>
455438
/// <param name="reader">The resulting reader end of the pipe.</param>
456439
/// <param name="writer">The resulting writer end of the pipe.</param>

src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,8 @@ public static void CreateDirectory(string fullPath)
298298
{
299299
return; // Path already exists and it's a directory.
300300
}
301-
else if (errorInfo.Error == Interop.Error.ENOENT)
301+
else if (errorInfo.Error == Interop.Error.ENOENT) // Some parts of the path don't exist yet.
302302
{
303-
// Some parts of the path don't exist yet.
304303
CreateParentsAndDirectory(fullPath);
305304
}
306305
else
@@ -309,20 +308,19 @@ public static void CreateDirectory(string fullPath)
309308
}
310309
}
311310

312-
public static void CreateParentsAndDirectory(string fullPath)
311+
private static void CreateParentsAndDirectory(string fullPath)
313312
{
314313
// Try create parents bottom to top and track those that could not
315314
// be created due to missing parents. Then create them top to bottom.
316-
List<string> stackDir = new List<string>();
317-
318-
stackDir.Add(fullPath);
315+
using ValueListBuilder<int> stackDir = new(stackalloc int[32]); // 32 arbitrarily chosen
316+
stackDir.Append(fullPath.Length);
319317

320318
int i = fullPath.Length - 1;
321-
// Trim trailing separator.
322319
if (PathInternal.IsDirectorySeparator(fullPath[i]))
323320
{
324-
i--;
321+
i--; // Trim trailing separator.
325322
}
323+
326324
do
327325
{
328326
// Find the end of the parent directory.
@@ -332,13 +330,11 @@ public static void CreateParentsAndDirectory(string fullPath)
332330
i--;
333331
}
334332

335-
// Try create it.
336-
string mkdirPath = fullPath.Substring(0, i);
333+
ReadOnlySpan<char> mkdirPath = fullPath.AsSpan(0, i);
337334
int result = Interop.Sys.MkDir(mkdirPath, (int)Interop.Sys.Permissions.Mask);
338335
if (result == 0)
339336
{
340-
// Created parent.
341-
break;
337+
break; // Created parent.
342338
}
343339

344340
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
@@ -348,7 +344,7 @@ public static void CreateParentsAndDirectory(string fullPath)
348344
// We'll try to create its parent on the next iteration.
349345

350346
// Track this path for later creation.
351-
stackDir.Add(mkdirPath);
347+
stackDir.Append(mkdirPath.Length);
352348
}
353349
else if (errorInfo.Error == Interop.Error.EEXIST)
354350
{
@@ -358,15 +354,15 @@ public static void CreateParentsAndDirectory(string fullPath)
358354
}
359355
else
360356
{
361-
throw Interop.GetExceptionForIoErrno(errorInfo, mkdirPath, isDirectory: true);
357+
throw Interop.GetExceptionForIoErrno(errorInfo, mkdirPath.ToString(), isDirectory: true);
362358
}
363359
i--;
364360
} while (i > 0);
365361

366362
// Create directories that had missing parents.
367-
for (i = stackDir.Count - 1; i >= 0; i--)
363+
for (i = stackDir.Length - 1; i >= 0; i--)
368364
{
369-
string mkdirPath = stackDir[i];
365+
ReadOnlySpan<char> mkdirPath = fullPath.AsSpan(0, stackDir[i]);
370366
int result = Interop.Sys.MkDir(mkdirPath, (int)Interop.Sys.Permissions.Mask);
371367
if (result < 0)
372368
{
@@ -386,7 +382,7 @@ public static void CreateParentsAndDirectory(string fullPath)
386382
}
387383
}
388384

389-
throw Interop.GetExceptionForIoErrno(errorInfo, mkdirPath, isDirectory: true);
385+
throw Interop.GetExceptionForIoErrno(errorInfo, mkdirPath.ToString(), isDirectory: true);
390386
}
391387
}
392388
}

0 commit comments

Comments
 (0)