Microsoft.IO.RecyclableMemoryStream

repository·master·Indexed 24 days ago

https://github.com/microsoft/microsoft.io.recyclablememorystream

A high-performance .NET library providing pooled MemoryStream implementations to reduce Garbage Collection (GC) overhead and Large Object Heap (LOH) allocations. It features the RecyclableMemoryStreamManager for coordinating small and large buffer pools, supports modern memory types like Span<byte> and ReadOnlySequence<byte>, and implements IBufferWriter<byte> for zero-copy writing.

Tokens
29.1K
Snippets
133
Records
210
Agent score
79%

What's inside Microsoft.IO.RecyclableMemoryStream

  1. What is Microsoft.IO.RecyclableMemoryStream?

    master

    Microsoft.IO.RecyclableMemoryStream is a high-performance replacement for System.IO.MemoryStream. It is designed to improve application performance and reduce Garbage Collection (GC) overhead by pooling underlying buffers instead of pooling the stream objects themselves.

    Key Benefits:

    • Reduces LOH Allocations: Uses pooled buffers to avoid Large Object Heap fragmentation.
    • Lower GC Pressure: Reduces Gen 2 collections and GC pause times.
    • Memory Safety: Includes bounded pool sizes to prevent memory leaks and fragmentation.
    • High Performance: Supports modern memory types like Span<byte>, ReadOnlySpan<byte>, ReadOnlySequence<byte>, and Memory<byte>.
    • Observability: Provides metrics, logging, and debug features like call stack recording for leaked streams.
  2. Use RecyclableMemoryStream for efficient memory management

    master

    The Microsoft.IO namespace provides two primary classes for managing memory streams efficiently while reducing pressure on the Garbage Collector (GC):

    • RecyclableMemoryStream: A MemoryStream implementation designed to handle potentially large buffers by using pooled memory.
    • RecyclableMemoryStreamManager: The central manager responsible for maintaining and managing pools of RecyclableMemoryStream objects.
  3. Configure RecyclableMemoryStreamManager behavior using Options

    master

    The Options class allows you to customize the behavior of a RecyclableMemoryStreamManager. You can use the default constructors to create an Options object with standard settings or use the parameterized constructor to specify common options immediately.

    Common configuration areas include:

    • Pooling limits: Controlling MaximumBufferSize, MaximumSmallPoolFreeBytes, and MaximumLargePoolFreeBytes to manage how much memory is kept in pools.
    • Buffer sizing: Adjusting BlockSize and LargeBufferMultiple to define how memory blocks are allocated.
    • Safety and Debugging: Using GenerateCallStacks for debugging (not for production) or ThrowExceptionOnToArray to prevent expensive ToArray() calls.
  4. Configure Large Pool strategies: Linear vs Exponential

    master

    When configuring the RecyclableMemoryStreamManager, you can choose between two strategies for the Large Pool to match your application's memory usage patterns:

    • Linear (Default): You specify a multiple and a maximum size. The pool contains an array of buffers that grow linearly (e.g., 1MB, 2MB, 3MB... up to a maximum). This is suitable for unpredictable large buffer sizes.
    • Exponential: Buffers double in size for each slot (e.g., 256KB, 512KB, 1MB, 2MB, 4MB, 8MB). This is more memory-efficient if you have many streams that are likely to stay within smaller size ranges.
  5. Understand Concurrency rules for RecyclableMemoryStream

    master

    When using this library in multi-threaded environments, observe the following rules:

    • RecyclableMemoryStream: Concurrent use of individual stream objects is not supported under any circumstances.
    • RecyclableMemoryStreamManager: This manager is thread-safe and can be used to retrieve streams across multiple threads safely.
  6. Monitor RecyclableMemoryStream via ETW events

    master

    The RecyclableMemoryStreamManager.Events class provides Event Tracing for Windows (ETW) events for RecyclableMemoryStream. You can use these events to monitor the lifecycle and performance of memory streams, such as buffer creation, disposal, and capacity issues.

    All events are written through the static Writer property.

  7. Security and Performance: Zeroing out buffers

    master

    By default, for performance reasons, buffers are not pre-initialized or zeroed-out when recycled. It is the developer's responsibility to ensure that data from a previous use of a buffer does not leak into a new operation.

    If your application requires protection against accidental data leakage, you can set ZeroOutBuffer to true. Note that this will incur a performance penalty as buffers will be zeroed out upon allocation and before being returned to the pool.

  8. Avoid ToArray and GetBuffer for performance

    master

    RecyclableMemoryStream is optimized for chained small pool blocks. Using methods that require contiguous memory can negate the benefits of the library:

    • ToArray: Avoid this. It always copies data into a new, non-pooled array. Using ToArray is considered a bug in high-performance scenarios. You can configure RecyclableStreamManager.ThrowExceptionOnToArray = true to prevent its use.
    • GetBuffer: If the stream uses multiple blocks, this method will copy them into a single large pool buffer. Always use the Length property to determine usable data size.
  9. Debug stream usage with Stream Identification

    master

    To help identify the source and lifetime of streams, RecyclableMemoryStream uses two identification mechanisms:

    1. GUID: Each stream object is assigned a unique GUID that identifies it throughout its entire lifetime.
    2. Tag: You can optionally assign an arbitrary string (a tag) when requesting a stream from the manager. This is useful for labeling streams with a class name or function name to track where in your code the stream originated. Note that tags are not unique; multiple streams can share the same tag.

    These identifiers are included in ETW events to help you trace issues.

  10. How RecyclableMemoryStreamManager works

    master

    The RecyclableMemoryStreamManager is the central thread-safe coordinator that manages two distinct buffer pools:

    1. Small Pool: Holds small, configurable buffers used for standard read/write operations. Multiple small buffers are chained together to represent a single stream.
    2. Large Pool: Holds large, contiguous buffers. These are used when a single contiguous buffer is required, such as when calling GetBuffer().

    Buffer Allocation and Lifecycle

    • On-Demand: Buffers are created only when requested and no suitable buffer exists in the pool.
    • Disposal: Buffers are returned to the pool when the RecyclableMemoryStream.Dispose() method is called.
    • Pool Limits: The manager uses MaximumFreeSmallPoolBytes and MaximumFreeLargePoolBytes to decide whether to keep a buffer in the pool or let it be garbage collected. Setting these to 0 allows unbounded growth, which can lead to memory leaks.
    • Contiguous Buffers: If you call GetBuffer() on a stream composed of small chained buffers, the manager will convert them into a single large buffer from the Large Pool.
    • Initial Capacity: You can request a stream with an initial capacity. If the capacity exceeds the small pool block size, the stream will use chained blocks unless you specify asContiguousBuffer: true, which forces the use of a single large buffer from the start.
  11. How RecyclableMemoryStream manages memory blocks

    master

    The stream is implemented using a series of uniformly-sized blocks. As the stream grows, additional blocks are retrieved from the RecyclableMemoryStreamManager. These blocks are pooled.

    The GetBuffer transition: GetBuffer() requires a single contiguous buffer.

    1. If only one block is in use, that block is returned.
    2. If multiple blocks are in use, the stream retrieves a larger, contiguous buffer from the memory manager (which are also pooled and sized as multiples of a chunk size, typically 1 MB).
    3. Once a stream transitions to using a large contiguous buffer, it will never use the small blocks again. All operations then occur on the large buffer.

    Limitations:

    • If the stream is longer than the maximum allowable array length in .NET, it can only be used via the Read/Write APIs using the block-based implementation. Attempting to convert it to a single buffer (via GetBuffer) will result in an exception.
    • If a stream has already transitioned to a single large buffer, it cannot grow beyond the maximum allowable array size supported by .NET.
  12. Basic usage of RecyclableMemoryStream

    master

    To use the library, instantiate a RecyclableMemoryStreamManager once and reuse it for the lifetime of your process. You can then request streams using manager.GetStream().

    Important: RecyclableMemoryStreamManager should be a long-lived object (e.g., a static field) to ensure effective pooling.

    class Program
    {
        private static readonly RecyclableMemoryStreamManager manager = new RecyclableMemoryStreamManager();
    
        static void Main(string[] args)
        {
            var sourceBuffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
            
            using (var stream = manager.GetStream())
            {
                stream.Write(sourceBuffer, 0, sourceBuffer.Length);
            }
        }
    }