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

[API Proposal]: Stream APIs - GetDirectSpan & ReadDirectSpan #107090

Closed
conmaster2112 opened this issue Aug 28, 2024 · 3 comments
Closed

[API Proposal]: Stream APIs - GetDirectSpan & ReadDirectSpan #107090

conmaster2112 opened this issue Aug 28, 2024 · 3 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.IO
Milestone

Comments

@conmaster2112
Copy link

Background and motivation

I've been using Stream APIs for some time now and in many cases memory is often copied when reading, and for that reason there are internal functions that solve these shortcomings, although some internal functions solve this, but they are not available to us as end users.

I would like to talk about the relationship between BinaryReader and MemoryStream in details.

BinaryReader

As for BinaryReader, it is a very useful class that could also be extended by child classes. Did i say could? So it's not possible? As for C# rules, sure, but if I want to add or override an existing write method, I end up with very disgusting performance, because it's fast natively, it's only thanks to internal workarounds.

Resources And Links

Yes, I can create my own MemoryStream and my own BinaryWriter/BinaryReader, but then it loses its compatibility with both 3rd-party and native libraries.

Solution

The answer to these things would be two new methods for the base Steam class.

  • void virtual ReadOnlySpan<byte> DirectRead(int length)
  • void virtual Span<byte> DirectGet(int length, bool moveCursor = true)
    Both of these methods should all round speed up the old stream APIs and avoid unnecessary heap memory allocation and unnecessary memory copying.

API Proposal

Proposal Concept

namespace System.IO;


public abstract class Stream
{
    public virtual bool CanDirectRead { get => false; }
    public virtual ReadOnlySpan<byte> DirectRead(int length)
    {
        throw new NotImplementedException();
    }
    public virtual Span<byte> DirectGet(int length, bool moveCursor)
    {
        throw new NotImplementedException();
    }
}
public class MemoryStream: Stream
{
    public override bool CanDirectRead { get => true; }
    public override ReadOnlySpan<byte> DirectRead(int length)
    {
        EnsureNotClosed();

        int origPos = _position;
        int newPos = origPos + count;

        if ((uint)newPos > (uint)_length)
        {
            _position = _length;
            ThrowHelper.ThrowEndOfFileException();
        }

        var span = new ReadOnlySpan<byte>(_buffer, origPos, count);
        _position = newPos;
        return span;
    }
    public override Span<byte> DirectGet(int length, bool moveCursor = true)
    {

        EnsureNotClosed();

        // if(!CanWrite) throw ... 
        
        int origPos = _position;
        int newPos = origPos + count;

        if ((uint)newPos > (uint)_length)
        {
            _position = _length;
            ThrowHelper.ThrowEndOfFileException();
        }

        var span = new Span<byte>(_buffer, origPos, count);
        if(moveCursor) _position = newPos;
        return span;
    }
}

public class BinaryReader
{
    //Internal re-implementation
    private ReadOnlySpan<byte> InternalRead(Span<byte> buffer)
    {
        Debug.Assert(buffer.Length != 1, "length of 1 should use ReadByte.");

        if (_stream.CanDirectRead)
        {
            /* Previose method
                // read directly from MemoryStream buffer
                Debug.Assert(_stream is MemoryStream);
                return Unsafe.As<MemoryStream>(_stream).InternalReadSpan(buffer.Length);
            */
            return _stream.DirectRead(buffer.Length);
        }
        else
        {
            ThrowIfDisposed();

            _stream.ReadExactly(buffer);

            return buffer;
        }
    }
}

API Usage

Pseudo Concept Example - Custom Binary Reader

//Initialize own exteneded implementation of BinaryReader
using MyBinaryReader reader = new MyBinaryReader(new MemoryStream(packet));

//Read int in my own way!!!
int myInt = reader.ReadInt32BigEndian();

Pseudo Concept Example - Custom MemoryStream

//Initialize BinaryReader with own MemoryStream implementation
using BinaryReader reader = new BinaryReader(new MyMemoryStream(packet));

//Read int really fast!!!
int myInt = reader.ReadInt32();

Alternative Designs

The examples given above solve two birds with one stone, both the acceleration of reading from MemoryStream and the extensibility of BineryReader.

Another possibility would be to publish the internal method InternalRead in the BineryReader class, but that would not change anything, since the binary reader would also read slowly from memory streams other than the native one. (Its Hardcoded)

Also keep in mind that descendant of the MemoryStream class is not marked as MemoryStream for BinaryReader as well, so that results in slow reading from descendants of MemoryStream class.

Risks

The method name can be misleading for beginners, and for most stream implementations it will throw an exception NotImplementedException.

@conmaster2112 conmaster2112 added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Aug 28, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Aug 28, 2024
Copy link
Contributor

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

@jozkee
Copy link
Member

jozkee commented Aug 28, 2024

Related: #106524.

@jozkee jozkee added this to the Future milestone Aug 28, 2024
@conmaster2112
Copy link
Author

Thanks, well let me close it for now.

@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Aug 28, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Sep 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.IO
Projects
None yet
Development

No branches or pull requests

2 participants