Skip to content

Conversation

@adamsitnik
Copy link
Member

Explanation:

  • when creating a memory mapped file, we don't need to fetch the file size multiple times. We can do it once and reduce the number of sys-calls.
  • there is no need to use FileStream, we can use more lightweight SafeFileHandle. It reduces the managed allocations.

Windows

Method Toolchain capacity Mean Ratio Allocated
CreateFromFile \main\corerun.exe 10000 43.132 us 1.00 448 B
CreateFromFile \pr\corerun.exe 10000 40.030 us 0.93 288 B
CreateFromFile \main\corerun.exe 100000 43.919 us 1.00 448 B
CreateFromFile \pr\corerun.exe 100000 40.961 us 0.93 288 B
CreateFromFile \main\corerun.exe 1000000 47.123 us 1.00 448 B
CreateFromFile \pr\corerun.exe 1000000 44.099 us 0.94 288 B
CreateFromFile \main\corerun.exe 10000000 57.808 us 1.00 448 B
CreateFromFile \pr\corerun.exe 10000000 54.653 us 0.94 288 B

Unix

Method Toolchain capacity Mean Ratio Allocated
CreateNew /main/corerun 10000 8.028 us 1.00 448 B
CreateNew /pr/corerun 10000 6.197 us 0.77 280 B
CreateFromFile /main/corerun 10000 43.379 us 1.00 480 B
CreateFromFile /pr/corerun 10000 35.422 us 0.83 312 B
CreateNew /main/corerun 100000 8.051 us 1.00 448 B
CreateNew /pr/corerun 100000 6.189 us 0.77 280 B
CreateFromFile /main/corerun 100000 39.091 us 1.00 480 B
CreateFromFile /pr/corerun 100000 35.072 us 0.90 312 B
CreateNew /main/corerun 1000000 7.765 us 1.00 448 B
CreateNew /pr/corerun 1000000 5.935 us 0.77 280 B
CreateFromFile /main/corerun 1000000 38.829 us 1.00 480 B
CreateFromFile /pr/corerun 1000000 35.471 us 0.91 312 B
CreateNew /main/corerun 10000000 7.913 us 1.00 448 B
CreateNew /pr/corerun 10000000 5.934 us 0.75 280 B
CreateFromFile /main/corerun 10000000 37.554 us 1.00 480 B
CreateFromFile /pr/corerun 10000000 34.940 us 0.93 312 B

Related to #62768

@adamsitnik adamsitnik added area-System.IO tenet-performance Performance related issue labels Jan 14, 2022
@adamsitnik adamsitnik added this to the 7.0.0 milestone Jan 14, 2022
@ghost ghost assigned adamsitnik Jan 14, 2022
@ghost
Copy link

ghost commented Jan 14, 2022

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Explanation:

  • when creating a memory mapped file, we don't need to fetch the file size multiple times. We can do it once and reduce the number of sys-calls.
  • there is no need to use FileStream, we can use more lightweight SafeFileHandle. It reduces the managed allocations.

Windows

Method Toolchain capacity Mean Ratio Allocated
CreateFromFile \main\corerun.exe 10000 43.132 us 1.00 448 B
CreateFromFile \pr\corerun.exe 10000 40.030 us 0.93 288 B
CreateFromFile \main\corerun.exe 100000 43.919 us 1.00 448 B
CreateFromFile \pr\corerun.exe 100000 40.961 us 0.93 288 B
CreateFromFile \main\corerun.exe 1000000 47.123 us 1.00 448 B
CreateFromFile \pr\corerun.exe 1000000 44.099 us 0.94 288 B
CreateFromFile \main\corerun.exe 10000000 57.808 us 1.00 448 B
CreateFromFile \pr\corerun.exe 10000000 54.653 us 0.94 288 B

Unix

Method Toolchain capacity Mean Ratio Allocated
CreateNew /main/corerun 10000 8.028 us 1.00 448 B
CreateNew /pr/corerun 10000 6.197 us 0.77 280 B
CreateFromFile /main/corerun 10000 43.379 us 1.00 480 B
CreateFromFile /pr/corerun 10000 35.422 us 0.83 312 B
CreateNew /main/corerun 100000 8.051 us 1.00 448 B
CreateNew /pr/corerun 100000 6.189 us 0.77 280 B
CreateFromFile /main/corerun 100000 39.091 us 1.00 480 B
CreateFromFile /pr/corerun 100000 35.072 us 0.90 312 B
CreateNew /main/corerun 1000000 7.765 us 1.00 448 B
CreateNew /pr/corerun 1000000 5.935 us 0.77 280 B
CreateFromFile /main/corerun 1000000 38.829 us 1.00 480 B
CreateFromFile /pr/corerun 1000000 35.471 us 0.91 312 B
CreateNew /main/corerun 10000000 7.913 us 1.00 448 B
CreateNew /pr/corerun 10000000 5.934 us 0.75 280 B
CreateFromFile /main/corerun 10000000 37.554 us 1.00 480 B
CreateFromFile /pr/corerun 10000000 34.940 us 0.93 312 B

Related to #62768

Author: adamsitnik
Assignees: -
Labels:

area-System.IO, tenet-performance

Milestone: 7.0.0

SetHandle(handlePtr);
}

protected override void Dispose(bool disposing)
Copy link
Member

Choose a reason for hiding this comment

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

Is this going be an observable behavior change for user defined FileStreams? The potential user-defined Dispose is not going to be called anymore.

Copy link
Member

@stephentoub stephentoub Jan 14, 2022

Choose a reason for hiding this comment

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

Yes, although that looks like it's already a discrepancy between our Windows and Unix implementation, with the Windows implementation not Dispose'ing of the file stream and the Unix implementation doing so (unless I'm missing it in the code). While it would then be a behavior change on Unix, as the Unix implementation is trying to match the pre-existing Windows semantics it seems like a reasonable bug fix.

long fileSize = mode switch
{
FileMode.CreateNew or FileMode.Create => 0, // the file is brand new
_ => RandomAccess.GetLength(fileHandle)
Copy link
Member

Choose a reason for hiding this comment

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

If GetLength throws, the file handle won't be Dispose'd.

Copy link
Member

Choose a reason for hiding this comment

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

Also, doesn't this throw for non-regular files? Is the exception that occurs in such a case the same as it was before?

Copy link
Member Author

Choose a reason for hiding this comment

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

If GetLength throws, the file handle won't be Dispose'd.

that is correct, I am going to add a try-catch block

Also, doesn't this throw for non-regular files?

both FileStream.Length and RandomAccess.GetLength throw for non-seekable files.

the special character device files report that they are seekable and of a size equal zero

Copy link
Member

Choose a reason for hiding this comment

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

Do we have tests that validate the exception in this case, both with capacity == 0 and capacity != 0?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do we have tests that validate the exception in this case, both with capacity == 0 and capacity != 0?

We don't. For both cases, the current implementation would throw for non-seekable file (without closing the handle).

For capacity == 0 here when accessing fileStream.Length:

For capacity != 0 here when accessing fileStream.Length:

if (access == MemoryMappedFileAccess.Read && capacity > fileStream.Length)
{
throw new ArgumentException(SR.Argument_ReadAccessWithLargeCapacity);
}
// one can always create a small view if they do not want to map an entire file
if (fileStream.Length > capacity)

Copy link
Member Author

Choose a reason for hiding this comment

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

@stephentoub I've sent a PR with tests #63927

Once it's gets merged, I am going to sync this PR with main branch.

Copy link
Member Author

Choose a reason for hiding this comment

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

@stephentoub I've merged the tests into this PR and they are all passing. PTAL

@adamsitnik adamsitnik force-pushed the moreMemoryMappedPerf branch from 37e5f05 to 59e4f24 Compare January 19, 2022 07:49
@adamsitnik adamsitnik merged commit a24364a into dotnet:main Feb 8, 2022
@adamsitnik adamsitnik deleted the moreMemoryMappedPerf branch February 8, 2022 09:17
@ghost ghost locked as resolved and limited conversation to collaborators Mar 10, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.IO tenet-performance Performance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants