Skip to content
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

Use UnixFileStream's ReadAsync implementation on Windows when !IsAsync #56682

Merged
merged 1 commit into from
Aug 2, 2021

Conversation

stephentoub
Copy link
Member

UnixFileStream's ReadAsync implementation uses a reusable IValueTaskSource implementation to avoid allocating a new work item on every read. We can push that implementation down to OSFileStreamStrategy, and then use it for the Windows implementation of ReadAsync as well when IsAsync==false, rather than delegating to the base Stream implementation.

This PR almost entirely just moves code around. The only change to logic is in RandomAccess.Windows.cs, to only set an offset into the NativeOverlapped if the SafeFileHandle is seekable; otherwise, it fails when used with pipes.

Method Toolchain Mean Error StdDev Ratio Gen 0 Allocated
Read \main\corerun.exe 36.19 ms 0.568 ms 0.531 ms 1.00 - 31 B
Read \pr\corerun.exe 35.71 ms 0.296 ms 0.263 ms 0.99 - 34 B
ReadAsync \main\corerun.exe 45.84 ms 0.360 ms 0.337 ms 1.00 90.9091 1,094,165 B
ReadAsync \pr\corerun.exe 42.74 ms 0.434 ms 0.406 ms 0.93 - 256 B
    private byte[] _buffer = new byte[4096];
    private FileStream _stream;

    [GlobalSetup]
    public void Setup()
    {
        byte[] bytes = new byte[40_000_000];
        new Random().NextBytes(bytes);

        string path = Path.GetTempFileName();
        File.WriteAllBytes(path, bytes);

        _stream = File.OpenRead(path);
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _stream.Close();
        File.Delete(_stream.Name);
    }

    [Benchmark]
    public void Read()
    {
        _stream.Position = 0;
        while (_stream.Read(_buffer) != 0) ;
    }

    [Benchmark]
    public async Task ReadAsync()
    {
        _stream.Position = 0;
        while (await _stream.ReadAsync(_buffer) != 0) ;
    }

cc: @adamsitnik

UnixFileStream's ReadAsync implementation uses a reusable IValueTaskSource implementation to avoid allocating a new work item on every read.  We can push that implementation down to OSFileStreamStrategy, and then use it for the Windows implementation of ReadAsync as well when IsAsync==false, rather than delegating to the base Stream implementation.

This PR almost entirely just moves code around.  The only change to logic is in RandomAccess.Windows.cs, to only set an offset into the NativeOverlapped if the SafeFileHandle is seekable; otherwise, it fails when used with pipes.
@stephentoub stephentoub added area-System.IO tenet-performance Performance related issue labels Aug 1, 2021
@stephentoub stephentoub added this to the 6.0.0 milestone Aug 1, 2021
@ghost
Copy link

ghost commented Aug 1, 2021

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

Issue Details

UnixFileStream's ReadAsync implementation uses a reusable IValueTaskSource implementation to avoid allocating a new work item on every read. We can push that implementation down to OSFileStreamStrategy, and then use it for the Windows implementation of ReadAsync as well when IsAsync==false, rather than delegating to the base Stream implementation.

This PR almost entirely just moves code around. The only change to logic is in RandomAccess.Windows.cs, to only set an offset into the NativeOverlapped if the SafeFileHandle is seekable; otherwise, it fails when used with pipes.

Method Toolchain Mean Error StdDev Ratio Gen 0 Allocated
Read \main\corerun.exe 36.19 ms 0.568 ms 0.531 ms 1.00 - 31 B
Read \pr\corerun.exe 35.71 ms 0.296 ms 0.263 ms 0.99 - 34 B
ReadAsync \main\corerun.exe 45.84 ms 0.360 ms 0.337 ms 1.00 90.9091 1,094,165 B
ReadAsync \pr\corerun.exe 42.74 ms 0.434 ms 0.406 ms 0.93 - 256 B
    private byte[] _buffer = new byte[4096];
    private FileStream _stream;

    [GlobalSetup]
    public void Setup()
    {
        byte[] bytes = new byte[40_000_000];
        new Random().NextBytes(bytes);

        string path = Path.GetTempFileName();
        File.WriteAllBytes(path, bytes);

        _stream = File.OpenRead(path);
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _stream.Close();
        File.Delete(_stream.Name);
    }

    [Benchmark]
    public void Read()
    {
        _stream.Position = 0;
        while (_stream.Read(_buffer) != 0) ;
    }

    [Benchmark]
    public async Task ReadAsync()
    {
        _stream.Position = 0;
        while (await _stream.ReadAsync(_buffer) != 0) ;
    }

cc: @adamsitnik

Author: stephentoub
Assignees: -
Labels:

area-System.IO, tenet-performance

Milestone: 6.0.0

Copy link
Member

@adamsitnik adamsitnik left a 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 !

return MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment) ?
new ValueTask(BeginWriteInternal(segment.Array!, segment.Offset, segment.Count, null, null, serializeAsynchronously: true, apm: false)) :
base.WriteAsync(buffer, cancellationToken);
}
Copy link
Member

Choose a reason for hiding this comment

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

after this code removal, SyncWindowsFileStreamStrategy and UnixFileStreamStrategy become de facto classes that do not change or extend the behaviour of OSFileStreamStrategy.

Have you considered the removal of SyncWindowsFileStreamStrategy and UnixFileStreamStrategy and using OSFileStreamStrategy directly?

Copy link
Member Author

Choose a reason for hiding this comment

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

No strong opinion. If you'd like to consolidate, go for it. If not, I'm ok keeping the derived types.

@adamsitnik
Copy link
Member

I am going to merge it now so I can get updated numbers for the blog post ;)

@adamsitnik adamsitnik merged commit 79c4144 into dotnet:main Aug 2, 2021
@adamsitnik
Copy link
Member

I am not sure if this was intentional, but after moving all the logic from Unix to OS strategy it's now much easier to work with the code on Windows. Refactoring in VS renames all usages of methods, and it's less likely for the code to compile fine on Windows and fail on Unix. 👍

@stephentoub stephentoub deleted the consolidatefslogic branch August 2, 2021 10:50
@ghost ghost locked as resolved and limited conversation to collaborators Sep 1, 2021
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.

2 participants