Saturday, April 24, 2021

Tool 1: BufferedStreamReader & BufferedStreamWriter

FileStream, the native implementation of I/O streaming is safe but slow. It’s a wrapper for the unmanaged CreateFile call and it’s protected by a deep layer of redundant argument checks. These constant validations are important and we shouldn’t bypass them. 

This won’t be much of a problem if we’re only moving large buffers on the stream, but will be painfully slow when we move only a few bytes of data at a time. It can be ignored if we are already using a streaming wrapper that internally buffers its memory moves, such as MessagePack.MessagePackStreamReader, or if we use MessagePack’s or Protobuf-net’s methods that take a stream as an argument, because they will internally perform such buffering. 

Solution: We use a buffer to delay calls to the underlying FileStream until we can move a reasonably-sized amount of data. 

Surprisingly, while FileStream itself is already buffered, that doesn’t greatly improve performance. We could wrap it in a BufferedStream, but it also won’t bring great benefits and BufferedStream will complain (sending trace warnings) when the underlying stream is already of the type FileStream. I’ve implemented BufferedStreamReader and BufferedStreamWriter, both inheriting from Stream and splitting the dual nature of stream I/O onto a read and a write context (This of course outright excludes scenarios where we want to both read and write to the stream at once, but normally it’s feasible to alternate between using one and the other mode of operation). 

The scenario where this buffering implementation offers the greatest benefits is when we’re the final users of the stream, and call its Read and Write methods directly, moving small number of bytes at a time.

Usage samples:

BufferedStreamReaderExamples.cs

BufferedStreamWriterExamples.cs

 

 

 


No comments:

Post a Comment