Explain object pooling and when to use it.
Quick Answer
Object pooling reuses a set of pre-created objects instead of repeatedly allocating and collecting them, reducing GC pressure and allocation cost for expensive-to-create or frequently-used objects. .NET provides `ObjectPool<T>` and `ArrayPool<T>` for this. Use it for hot paths with high churn (buffers, large arrays, reusable workers); avoid it for cheap objects where pooling overhead and lifetime-management complexity outweigh the benefit.
Detailed Answer
Object Pooling is a design pattern that reuses objects instead of creating and destroying them repeatedly, reducing garbage collection pressure and improving performance.
How It Works:
- Create a pool of pre-initialized objects
- Rent an object when needed
- Return the object to the pool when done
- Reset the object state before returning to pool
Implementation Example:
// Using ObjectPool from Microsoft.Extensions.ObjectPool
public class BufferPool
{
private readonly ObjectPool _pool;
public BufferPool()
{
var policy = new DefaultPooledObjectPolicy
{
MaximumRetained = 100
};
_pool = new DefaultObjectPool(
new BufferPoolPolicy(),
100
);
}
public byte[] Rent()
{
return _pool.Get();
}
public void Return(byte[] buffer)
{
Array.Clear(buffer, 0, buffer.Length);
_pool.Return(buffer);
}
}
public class BufferPoolPolicy : IPooledObjectPolicy
{
public byte[] Create()
{
return new byte[4096];
}
public bool Return(byte[] obj)
{
Array.Clear(obj, 0, obj.Length);
return true;
}
}
Using ArrayPool<T>:
public void ProcessData()
{
// Rent array from pool
byte[] buffer = ArrayPool.Shared.Rent(1024);
try
{
// Use the buffer
ReadData(buffer);
ProcessBuffer(buffer);
}
finally
{
// Always return to pool
ArrayPool.Shared.Return(buffer, clearArray: true);
}
}
Custom Object Pool:
public class Connection
{
public void Open() { }
public void Close() { }
public void Reset() { }
}
public class ConnectionPool
{
private readonly ConcurrentBag _pool = new();
private readonly int _maxSize;
private int _count;
public ConnectionPool(int maxSize)
{
_maxSize = maxSize;
}
public Connection Rent()
{
if (_pool.TryTake(out var connection))
{
return connection;
}
if (_count < _maxSize)
{
Interlocked.Increment(ref _count);
return new Connection();
}
// Wait for available connection
SpinWait.SpinUntil(() => _pool.TryTake(out connection));
return connection;
}
public void Return(Connection connection)
{
connection.Reset();
_pool.Add(connection);
}
}
When to Use Object Pooling:
Use When:
- Objects are expensive to create (database connections, large arrays)
- Objects are created and destroyed frequently
- Garbage collection pressure is high
- Application has predictable object usage patterns
- Objects can be safely reused after resetting state
Good Candidates:
- Database connections
- HTTP client instances
- Large buffers or arrays
- StringBuilder instances
- MemoryStream objects
- Socket connections
- Encryption/hashing objects
Don't Use When:
- Objects are lightweight and cheap to create
- Object creation rate is low
- Objects cannot be safely reset or reused
- Pool management overhead exceeds creation cost
- Objects have complex state that's hard to reset
Benefits:
- Reduced GC pressure
- Improved performance
- Lower memory allocation
- Predictable memory usage
- Faster object acquisition
Considerations:
- Thread safety
- Proper cleanup/reset of pooled objects
- Pool size management
- Memory leaks if objects aren't returned
- Potential for holding onto memory longer than needed
Example Use Case:
public class ImageProcessor
{
private readonly ObjectPool _stringBuilderPool;
private readonly ArrayPool _byteArrayPool;
public async Task ProcessImageAsync(Stream imageStream)
{
// Rent pooled objects
var sb = _stringBuilderPool.Get();
var buffer = _byteArrayPool.Rent(8192);
try
{
int bytesRead;
while ((bytesRead = await imageStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
// Process buffer
sb.Append(Convert.ToBase64String(buffer, 0, bytesRead));
}
return sb.ToString();
}
finally
{
// Always return to pools
sb.Clear();
_stringBuilderPool.Return(sb);
_byteArrayPool.Return(buffer, clearArray: true);
}
}
}
Built-in .NET Pools:
ArrayPool<T>.SharedMemoryPool<T>.SharedObjectPool<T>(Microsoft.Extensions.ObjectPool)- HttpClient connection pooling (automatic)
- Thread pool