Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Use Stream's BeginRead/WriteInternal from FileStream #27737

Merged
merged 1 commit into from
Nov 11, 2019

Conversation

stephentoub
Copy link
Member

FileStream has two modes, decided upon at construction time. When it's created in non-async mode, the Read/WriteAsync methods end up queueing work items to invoke the synchronous Read/Write methods. To do this, the base methods on Stream delegate to Begin/EndRead/Write (since they were around first) and then the resulting IAsyncResult is wrapped. However, Stream has an optimization that checks to see whether the derived stream actually overrides Begin/EndXx, and if it doesn't, then it skips using those and goes straight to queueing a work item to Read/Write. However, FileStream does override those, but when it delegates to the base implementation because it's in non-async mode (rather than because it's a type derived from FileStream), going through Begin/EndXx is just unnecessary overhead. So, in the right circumstances, we can call to Stream's special helper instead.

cc: @JeremyKuhne
Fixes https://github.com/dotnet/corefx/issues/42419

Method Toolchain Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
ReadAll \new\corerun.exe 32.44 ms 0.312 ms 0.292 ms 0.94 0.01 125.0000 - - 1.09 MB
ReadAll \old\corerun.exe 34.64 ms 0.248 ms 0.232 ms 1.00 0.00 266.6667 - - 1.95 MB
WriteAll \new\corerun.exe 33.52 ms 0.465 ms 0.435 ms 0.96 0.02 125.0000 - - 1.09 MB
WriteAll \old\corerun.exe 34.87 ms 0.259 ms 0.216 ms 1.00 0.00 266.6667 - - 1.95 MB
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Running;
using System;
using System.IO;
using System.Threading.Tasks;

[MemoryDiagnoser]
public class Program
{
    static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);

    private string _path;
    private Stream _stream;

    private const int NumSegments = 10_240;

    private static byte[] _buffer = new byte[1024];

    [GlobalSetup]
    public void Setup()
    {
        new Random(42).NextBytes(_buffer);

        _path = Path.GetTempFileName();
        using (var fs = File.OpenWrite(_path))
        {
            for (int i = 0; i < NumSegments; i++)
            {
                fs.Write(_buffer, 0, _buffer.Length);
            }
        }

        _stream = new FileStream(_path, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 1, useAsync: false);
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _stream.Dispose();
        File.Delete(_path);
    }

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

    [Benchmark]
    public async Task WriteAll()
    {
        _stream.Position = 0;
        for (int i = 0; i < NumSegments; i++)
        {
            await _stream.WriteAsync(_buffer, 0, _buffer.Length);
        }
    }
}

@stephentoub stephentoub changed the title Use Stream's BeginReadInternal from FileStream Use Stream's BeginRead/WriteInternal from FileStream Nov 7, 2019
Copy link
Member

@JeremyKuhne JeremyKuhne left a comment

Choose a reason for hiding this comment

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

Nice! Thanks, @stephentoub!

FileStream has two modes, decided upon at construction time.  When it's created in non-async mode, the Read/WriteAsync methods end up queueing work items to invoke the synchronous Read/Write methods.  To do this, the base methods on Stream delegate to Begin/EndRead/Write (since they were around first) and then the resulting IAsyncResult is wrapped.  However, Stream has an optimization that checks to see whether the derived stream actually overrides Begin/EndXx, and if it doesn't, then it skips using those and goes straight to queueing a work item to Read/Write.  However, FileStream does override those, but when it delegates to the base implementation because it's in non-async mode (rather than because it's a type derived from FileStream), going through Begin/EndXx is just unnecessary overhead.  So, in the right circumstances, we can call to Stream's special helper instead.
@stephentoub stephentoub merged commit e2741eb into dotnet:master Nov 11, 2019
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corefx that referenced this pull request Nov 11, 2019
FileStream has two modes, decided upon at construction time.  When it's created in non-async mode, the Read/WriteAsync methods end up queueing work items to invoke the synchronous Read/Write methods.  To do this, the base methods on Stream delegate to Begin/EndRead/Write (since they were around first) and then the resulting IAsyncResult is wrapped.  However, Stream has an optimization that checks to see whether the derived stream actually overrides Begin/EndXx, and if it doesn't, then it skips using those and goes straight to queueing a work item to Read/Write.  However, FileStream does override those, but when it delegates to the base implementation because it's in non-async mode (rather than because it's a type derived from FileStream), going through Begin/EndXx is just unnecessary overhead.  So, in the right circumstances, we can call to Stream's special helper instead.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
stephentoub added a commit to dotnet/corefx that referenced this pull request Nov 11, 2019
FileStream has two modes, decided upon at construction time.  When it's created in non-async mode, the Read/WriteAsync methods end up queueing work items to invoke the synchronous Read/Write methods.  To do this, the base methods on Stream delegate to Begin/EndRead/Write (since they were around first) and then the resulting IAsyncResult is wrapped.  However, Stream has an optimization that checks to see whether the derived stream actually overrides Begin/EndXx, and if it doesn't, then it skips using those and goes straight to queueing a work item to Read/Write.  However, FileStream does override those, but when it delegates to the base implementation because it's in non-async mode (rather than because it's a type derived from FileStream), going through Begin/EndXx is just unnecessary overhead.  So, in the right circumstances, we can call to Stream's special helper instead.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corert that referenced this pull request Nov 12, 2019
FileStream has two modes, decided upon at construction time.  When it's created in non-async mode, the Read/WriteAsync methods end up queueing work items to invoke the synchronous Read/Write methods.  To do this, the base methods on Stream delegate to Begin/EndRead/Write (since they were around first) and then the resulting IAsyncResult is wrapped.  However, Stream has an optimization that checks to see whether the derived stream actually overrides Begin/EndXx, and if it doesn't, then it skips using those and goes straight to queueing a work item to Read/Write.  However, FileStream does override those, but when it delegates to the base implementation because it's in non-async mode (rather than because it's a type derived from FileStream), going through Begin/EndXx is just unnecessary overhead.  So, in the right circumstances, we can call to Stream's special helper instead.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
jkotas pushed a commit to dotnet/corert that referenced this pull request Nov 12, 2019
FileStream has two modes, decided upon at construction time.  When it's created in non-async mode, the Read/WriteAsync methods end up queueing work items to invoke the synchronous Read/Write methods.  To do this, the base methods on Stream delegate to Begin/EndRead/Write (since they were around first) and then the resulting IAsyncResult is wrapped.  However, Stream has an optimization that checks to see whether the derived stream actually overrides Begin/EndXx, and if it doesn't, then it skips using those and goes straight to queueing a work item to Read/Write.  However, FileStream does override those, but when it delegates to the base implementation because it's in non-async mode (rather than because it's a type derived from FileStream), going through Begin/EndXx is just unnecessary overhead.  So, in the right circumstances, we can call to Stream's special helper instead.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/mono that referenced this pull request Nov 14, 2019
FileStream has two modes, decided upon at construction time.  When it's created in non-async mode, the Read/WriteAsync methods end up queueing work items to invoke the synchronous Read/Write methods.  To do this, the base methods on Stream delegate to Begin/EndRead/Write (since they were around first) and then the resulting IAsyncResult is wrapped.  However, Stream has an optimization that checks to see whether the derived stream actually overrides Begin/EndXx, and if it doesn't, then it skips using those and goes straight to queueing a work item to Read/Write.  However, FileStream does override those, but when it delegates to the base implementation because it's in non-async mode (rather than because it's a type derived from FileStream), going through Begin/EndXx is just unnecessary overhead.  So, in the right circumstances, we can call to Stream's special helper instead.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
akoeplinger pushed a commit to mono/mono that referenced this pull request Nov 15, 2019
FileStream has two modes, decided upon at construction time.  When it's created in non-async mode, the Read/WriteAsync methods end up queueing work items to invoke the synchronous Read/Write methods.  To do this, the base methods on Stream delegate to Begin/EndRead/Write (since they were around first) and then the resulting IAsyncResult is wrapped.  However, Stream has an optimization that checks to see whether the derived stream actually overrides Begin/EndXx, and if it doesn't, then it skips using those and goes straight to queueing a work item to Read/Write.  However, FileStream does override those, but when it delegates to the base implementation because it's in non-async mode (rather than because it's a type derived from FileStream), going through Begin/EndXx is just unnecessary overhead.  So, in the right circumstances, we can call to Stream's special helper instead.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
@stephentoub stephentoub deleted the fsfastpath branch November 23, 2019 18:19
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.

Non-async FileStream does not use fast path from Stream for Read/WriteAsync
4 participants