Options

edit

PooledMemoryStreamOptions configures the sizes of the blocks a stream rents and how the pool keeps them. Streams created without options use PooledMemoryStreamOptions.Default.

Configure the options

var options = new PooledMemoryStreamOptions
{
    // Any number of strictly ascending tiers (bytes). Small streams use the smallest size and
    // grow through the larger ones; buffers bigger than the largest tier round up to a multiple of it.
    BufferSizes = [4 * 1024, 128 * 1024, 1024 * 1024, 10 * 1024 * 1024], // 4 KiB / 128 KiB / 1 MiB / 10 MiB
    MaxRetainedBytesPerBucket = 64L * 1024 * 1024,
    ClearOnReturn = false,
};

using var stream = new PooledMemoryStream(options);
PropertyDefaultMeaning
BufferSizes4 KiB, 64 KiB, 1 MiBThe sizes of the blocks, in bytes, in strictly ascending order
MaxRetainedBytesPerBucket16 MiBThe number of bytes the pool keeps for each block size
ClearOnReturnfalseWhether blocks are zeroed when they go back to the pool

Block sizes

A stream only rents blocks of the sizes listed in BufferSizes. A small stream uses the smallest size, and a growing stream moves to the larger sizes, so a large stream has a bounded number of blocks. When a single contiguous buffer larger than the largest size is needed, for instance for GetBuffer on a large stream, its size is rounded up to a multiple of the largest size.

  • BufferSizes must contain at least one size, every size must be greater than 0, and the sizes must be in strictly ascending order. Otherwise, setting the property throws.
  • Choose the smallest size from the size of your typical small stream, and the largest from your typical large stream. Fewer sizes make the pooled arrays more reusable between streams.

Pool size

MaxRetainedBytesPerBucket caps the memory the pool keeps for each block size. A block returned when its size already reaches the cap is left to the garbage collector. Raise it when many streams are alive at the same time, lower it to keep less memory when streams are rare. The pool is shared by every stream of the process, whatever their options.

Clear the blocks

The pool is shared, so a block can be rented by another stream after yours. With ClearOnReturn, the blocks your stream returns are zeroed first, so the next stream cannot see your data. This is slower, and only affects the blocks this stream returns, not the ones it rents. Enable it for streams that hold sensitive data.

The members of the stream never expose data written by another stream, whatever this setting: reads stop at Length, and GetBuffer zeroes the bytes past Length. The one exception is the uninitialized region returned by the IBufferWriter<byte> members.

Share the options

An options instance is frozen the first time it is used to create a stream: setting a property afterwards throws an InvalidOperationException. PooledMemoryStreamOptions.Default is always frozen. Configure every property first, then share the instance between your streams:

// Create the options once, and share them between the streams
var options = new PooledMemoryStreamOptions
{
    BufferSizes = [16 * 1024, 256 * 1024],
    ClearOnReturn = true, // zero the buffers before another stream can rent them
};

using (var first = new PooledMemoryStream(options))
{
    first.Write("secret"u8);
}

using var second = new PooledMemoryStream(options);

// Creating a stream froze the options: they cannot change any more
Assert.True(options.IsFrozen);
Assert.Throws<InvalidOperationException>(() => options.ClearOnReturn = false);

IsFrozen tells whether an instance can still be changed.