-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve Directory.CreateDirectory performance #73942
Conversation
This does a few things: - On all OSes, makes DirectoryInfo.Name and FileInfo.Name lazy. For a case like `DirectoryInfo.CreateDirectory(fullPath)` where the returned `DirectoryInfo` is ignored, previously we would still promptly allocate a `string` for the name, and now we won't, waiting instead until the property is accessed. - On Windows, after checking whether the target directory exists and finding it doesn't, rather than proceeding with the full logic to parse the path, find the first segment that exists, and then create them all, it adds a fast-path check to just try to create the target, under the assumption that creating a directory in a directory that already exists is very common. In such a case, it measurably speeds up throughput. If the parent doesn't already exist, this does add one syscall of overhead, and it can be measurable, but it's relatively small compared to the other costs involved on this slower path. - On Windows, on the slow path rather than allocating a `List<string>` to store the segments, it uses a `ValueListBuilder<string>` seeded with some stack space. - Fixes the nullable annotation on the internal DirectoryExists helper - Normalizes some parameter names
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsThis does a few things:
Contributes to #61954
|
@tmds, in #58799, how much of a regression did you see on Linux for CreateDirectory when the path already exists? I didn't see numbers for that in the PR nor the concerns about that regression addressed. I tried something similar on Windows, and opted against it as it ended up regressing that case by almost 2x, and I expect that's a reasonably common case. |
private string _exists;
private string _doesntExist;
private string _doesntExistChild;
[GlobalSetup]
public void Setup()
{
_exists = Path.GetTempFileName();
File.Delete(_exists);
Directory.CreateDirectory(_exists);
_doesntExist = Path.GetRandomFileName();
_doesntExistChild = Path.Combine(_doesntExist, Path.GetRandomFileName());
}
[GlobalCleanup]
public void Cleanup()
{
Directory.Delete(_exists);
}
[Benchmark]
public void Exists()
{
Directory.CreateDirectory(_exists);
}
[Benchmark]
public void DoesntExist()
{
Directory.CreateDirectory(_doesntExist).Delete();
}
[Benchmark]
public void ParentDoesntExist()
{
Directory.CreateDirectory(_doesntExistChild).Delete();
Directory.Delete(_doesntExist);
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thank you @stephentoub!
|
||
int lengthRoot = PathInternal.GetRootLength(fullPath.AsSpan()); | ||
bool somepathexists = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I know it was named like this before your changes
bool somepathexists = false; | |
bool somePathExists = false; |
} | ||
|
||
return name; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove whitespace
This is hitting failures on pre-Windows 10 OSes due to some kind of device path validation issue. I'm going to close it for now. I separated out the FileInfo/DirectoryInfo.Name laziness part into #73983. |
I had mentioned this regression in the initial comment. I expect it to be similar: 2x slower because it is making 2 syscalls instead of 1. I opted to optimize the non-exists case at the cost of the exists case because that improves new directory creation from a loop. A loop should be written so it doesn't create the same directory over and over. An |
This does a few things:
DirectoryInfo.CreateDirectory(fullPath)
where the returnedDirectoryInfo
is ignored, previously we would still promptly allocate astring
for the name, and now we won't, waiting instead until the property is accessed.List<string>
to store the segments, it uses aValueListBuilder<string>
seeded with some stack space.Contributes to #61954