Skip to content
This repository has been archived by the owner on Nov 6, 2018. It is now read-only.

Reduce allocations in PhysicalFileProvider when GetDirectoryContents is called but not enumerated #240

Merged
merged 1 commit into from
Oct 11, 2016

Conversation

natemcmaster
Copy link
Contributor

@natemcmaster natemcmaster commented Oct 7, 2016

Resolves #224

Optimize PhysicalFileProvider for scenario when it is basically being used for Directory.Exists() (cough, looking at you, aspnet/StaticFiles).

Add singleton of NotFoundDirectoryContents.
Delay calling DirectoryInfo.EnumerateFileSystemInfos which does lots of up-front allocation.

Benchmark: 100 checks for directory that exists but contents not enumerated
Importantly, this isn't creating an enumerator. We just want to know if the directory exist.

bool exists;
for (var i = 0; i < 100; i++)
{
    var dirContents = provider.GetDirectoryContents("exists");
    exists = dirContents.Exists;
}

Before:

image

After:
image

Benchmark: 100 checks for directory that does not exists

bool exists;
for (var i = 0; i < 100; i++)
{
    var dirContents = provider.GetDirectoryContents("doesnotexist");
    exists = dirContents.Exists;
    var e = dirContents.GetEnumerator();
}

Before:
image

After:
image

cc @davidfowl @Tratcher

@natemcmaster
Copy link
Contributor Author

Typo. This resolves #224 not 214

@@ -13,6 +13,11 @@ namespace Microsoft.Extensions.FileProviders
public class NotFoundDirectoryContents : IDirectoryContents
{
/// <summary>
/// A shared instance of <see cref="NotFoundDirectoryContents"/>
/// </summary>
public static NotFoundDirectoryContents Singleton { get; } = new NotFoundDirectoryContents();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instance

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've use Singleton in abstractions, e.g. NullChangeToken.Singleton. Seems like they mean the same thing. Is there some nuance I'm missing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Never mind in that case.


private static IDirectoryContents GetEnumerableContents(string fullPath)
{
return new EnumerableDirectoryContents(() =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we pass in the DirectoryInfo to EnumerableDirectoryContents and have it iterate it lazily (instead of passing in the Func?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but EnumerableDirectoryContents shared with providers that don't necessarily use the physical file system.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we fork it and make a PhysicalDirectoryContents? There's not much to implement in the interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that too and started an implementation with that approach, but ended up with the current implementation. There are probably a dozen ways it could be done, but I landed on this. I couldn't see a strong case for adding a new type such as PhysicalDirectoryContents.


private void EnsureInitialized()
{
LazyInitializer.EnsureInitialized(ref _entries, _entriesFactory);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably don't need any of the behavior of LazyInitializer. if (_entries == null) { .. }` should suffice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Will update.

@natemcmaster
Copy link
Contributor Author

Ping. @Tratcher can you also review?

});

return new EnumerableDirectoryContents(contents);
return GetEnumerableContents(fullPath);
}
catch (DirectoryNotFoundException)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these catches need to be moved within the closure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good catch. Due to our lazy instantiation of the enumerator, it's possible the directory could be deleted after we create the EnumerableDirectoryContents and before it's enumerated. I'll move it inside the closure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or duplicate it..

@natemcmaster natemcmaster force-pushed the namc/less-enumeration branch from 87aa57f to 35779b9 Compare October 11, 2016 21:45
@natemcmaster
Copy link
Contributor Author

🆙 📅

@natemcmaster natemcmaster force-pushed the namc/less-enumeration branch from 28754ae to a54ef9c Compare October 11, 2016 22:15
@natemcmaster natemcmaster merged commit a54ef9c into dev Oct 11, 2016
@natemcmaster natemcmaster deleted the namc/less-enumeration branch October 11, 2016 22:17
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants