Namespace: MiscUtil
.
Types involved: IBufferManager
, IBuffer
,
CachingBufferManager
, BufferAcquisitionException
, CachedBuffer
(internal)
Some programs make frequent use of potentially large (well, large enough to get on the Large Object Heap) byte arrays, particularly when processing streams of data. The buffer management classes in the miscellaneous utility library allow buffers to be reused to reduce pressure on the garbage collector.
The library specifies the IBufferManager
and IBuffer
interfaces, currently
supplying a single implementation for each of them. Each interface is very simple -
IBufferManager
has a single GetBuffer
method returning an IBuffer
,
and IBuffer
has a Bytes
property to retrieve the underlying byte array, and
implements IDisposable
to enable the client to relinquish control of the buffer.
When a client requests a buffer, it specifies the minimum size of buffer. Buffer managers may return
buffers larger than this, where appropriate. It is vital that clients call Dispose
on
a buffer when they have finished with it, so that the buffer manager may take control of the buffer,
reuse it if appropriate etc. Clients must not use references to either the buffer or the underlying
byte array after disposing of the buffer.
The single IBufferManager
implementation provided within the MiscUtil library is
CachingBufferManager
. This maintains a cache of buffers of increasing sizes, so that
when a request is made, an existing buffer is reused if it is available. These buffers are effectively
in "size bands", with the smallest size band of a large enough size being used - so with size bands of
1024, 2048 and 4096 bytes, for instance, a request for 1500 bytes would normally result in a 2048 byte buffer
being returned. Each band has a maximum number of buffers associated with it. The buffers are lazily
created as necessary - if only one buffer of a particular size is ever required at a time, only one buffer
of that size will be created.
An overload of the constructor takes a CachingBufferManager.Options
parameter to configure
the exact behaviour. The CachingBufferManager.Options
type has a number of properties,
defined below along with the default behaviour. If the parameterless constructor for
CachingBufferManager
is used, the defaults are all used for the manager.
CachingBufferManager.Options.BufferUnavailableAction
enum, with the following values:
ReturnUncached
- the default action, this returns a new buffer of exactly
the requested size, which isn't cached and will be garbage collected after use.
UseBigger
- the buffers in the next size band will be examined for availability,
and one returned if possible. If none are available, the next band up will be used etc, until
the maximum buffer size is reached, at which point an uncached buffer will be returned as if
ReturnUncached
were specified.
ThrowException
- a BufferAcquisitionException
is thrown.
BufferAcquisitionException
is thrown. The default is int.MaxValue
,
which is essentially "no limit" (as the requested buffer size is an int
anyway).
ActionOnBufferUnavailable
action to determine the next step if all buffers
in the requested size band are in use. Defaults to 16.
Here's a short program which demonstrates using a buffer manager to obtain buffers for copying data for streams. Only one buffer is ever created behind the scenes, to copy all the files.
using System.IO; using MiscUtil; /// <summary> /// Simple program to concatenate the files specified on the command /// line into a file "output.dat" /// </summary> class FileConcatenator { const int BufferSize = 8*1024; static void Main(string[] args) { CachingBufferManager.Options options = new CachingBufferManager.Options(); // We know we'll only use what we've copied into the buffer options.ClearAfterUse = false; IBufferManager manager = new CachingBufferManager(options); using (FileStream output = File.Create("output.dat")) { foreach (string inputName in args) { using (FileStream input = File.Open(inputName, FileMode.Open)) { CopyStream (input, output, manager); } } } } static void CopyStream (Stream input, Stream output, IBufferManager manager) { using (IBuffer buffer = manager.GetBuffer(BufferSize)) { byte[] data = buffer.Bytes; int read; // Use the actual size of the byte array, even if it's // more than we asked for while ( (read=input.Read(data, 0, data.Length)) > 0) { output.Write(data, 0, read); } } } } |
Back to the main MiscUtil page.