You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When reading from Streams, it's possible to create a more efficient specialized code-path in the case where the underlying Stream is a MemoryStream. This is done by accessing MemoryStream's underlying buffer directly, which avoids buffer copies into a destination buffer.
MemoryStream already contains an internal method, InternalReadSpan(), to facilitate this:
This returns a slice of the underlying MemoryStream buffer as a ROSpan and advances the stream by the appropriate amount. It is used internally by BinaryReader to perform efficient reads from MemoryStream without the unnecessary overhead of buffer copies.
By providing a public version of this method, third party code could make use of the same optimization that BinaryReader is currently afforded internally.
It is possible to implement a workaround as an extension method using MemoryStream's public methods:
This is impractical, since it only works if the MemoryStream was constructed with publiclyVisible = true, which sets _exposable = true. The most commonly used constructors set _exposable = false by default, causing TryGetBuffer() to return false and defeating the technique. The publiclyVisible property is intended to shield the underlying buffer from arbitrary reads and writes. This is not an issue for an official ReadSpanUnsafe() implementation that can return an ROSpan which only exposes read-only access to the actually "read" portion of the underlying buffer.
ReadSpanUnsafe() would work naturally with any methods that parse data from Span.
voidParseSomeData(MemoryStreammemoryStream){// Read a hex string from a MemoryStreamstringhexString=Convert.ToHexString(memoryStream.ReadSpanUnsafe(8));// Read a U16 BE from MemoryStreamushortbigEndianU16Value=BinaryPrimitives.ReadInt16BigEndian(memoryStream.ReadSpanUnsafe(2));// Read Double from MemoryStreamBinaryPrimitives.ReadDoubleBigEndian(memoryStream.ReadSpanUnsafe(8));}
It could also be used to efficiently extend BinaryReader with Big Endian parsing extension methods:
An alternative could be to allow the consumer to pass a delegate handler to the read method which provides the ROSpan buffer region as an argument, which constrains the lifetime of the ROSpan. The processed data could then be returned as an arbitrary TResult.
However this requires an additional delegate to be defined (since Func<ReadOnlySpan<byte>, TResult> is not legal), and has the added overhead of a delegate allocation.
Risks
The pitfall with the ReadSpanUnsafe() method is that the returned ROSpan's contents can become invalid if the memory in the underlying buffer is modified by a write to the stream. This will only happen if the MemoryStream position is moved backwards, and then a write is performed that overlaps with the portion of the buffer exposed by the ROSpan.
For this reason, the method name is appended with "Unsafe" to clearly indicate to the consumer that care should be taken when using the method. In practice, it would be unusual to hold onto the ROSpan from a previous read, while seeking and writing to the MemoryStream.
The text was updated successfully, but these errors were encountered:
Background and motivation
When reading from Streams, it's possible to create a more efficient specialized code-path in the case where the underlying Stream is a MemoryStream. This is done by accessing MemoryStream's underlying buffer directly, which avoids buffer copies into a destination buffer.
MemoryStream already contains an internal method,
InternalReadSpan()
, to facilitate this:This returns a slice of the underlying MemoryStream buffer as a ROSpan and advances the stream by the appropriate amount. It is used internally by BinaryReader to perform efficient reads from MemoryStream without the unnecessary overhead of buffer copies.
By providing a public version of this method, third party code could make use of the same optimization that BinaryReader is currently afforded internally.
It is possible to implement a workaround as an extension method using MemoryStream's public methods:
This is impractical, since it only works if the MemoryStream was constructed with
publiclyVisible = true
, which sets_exposable = true
. The most commonly used constructors set_exposable = false
by default, causingTryGetBuffer()
to return false and defeating the technique. ThepubliclyVisible
property is intended to shield the underlying buffer from arbitrary reads and writes. This is not an issue for an officialReadSpanUnsafe()
implementation that can return an ROSpan which only exposes read-only access to the actually "read" portion of the underlying buffer.API Proposal
API Usage
ReadSpanUnsafe()
would work naturally with any methods that parse data from Span.It could also be used to efficiently extend BinaryReader with Big Endian parsing extension methods:
Alternative Designs
An alternative could be to allow the consumer to pass a delegate handler to the read method which provides the ROSpan buffer region as an argument, which constrains the lifetime of the ROSpan. The processed data could then be returned as an arbitrary
TResult
.However this requires an additional delegate to be defined (since
Func<ReadOnlySpan<byte>, TResult>
is not legal), and has the added overhead of a delegate allocation.Risks
The pitfall with the
ReadSpanUnsafe()
method is that the returned ROSpan's contents can become invalid if the memory in the underlying buffer is modified by a write to the stream. This will only happen if the MemoryStream position is moved backwards, and then a write is performed that overlaps with the portion of the buffer exposed by the ROSpan.For this reason, the method name is appended with "Unsafe" to clearly indicate to the consumer that care should be taken when using the method. In practice, it would be unusual to hold onto the ROSpan from a previous read, while seeking and writing to the MemoryStream.
The text was updated successfully, but these errors were encountered: