Memory Buffer Pooling in Horse is a high-performance optimization designed to minimize memory allocation overhead (heap churn) and GC/Memory Manager lock contention under heavy concurrent workloads.
Normally, web frameworks allocate new memory structures for every HTTP request and response payload (using TMemoryStream or TBytesStream). In highly concurrent Delphi/FPC web applications, continuous allocation and deallocation (GetMem/FreeMem) can lead to:
- Heap Fragmentation: Splitting physical memory into tiny blocks.
- Lock Contention: Multi-threaded servers locking the memory manager global heap mutex while allocating memory, causing thread stall and reducing CPU efficiency.
Horse implements a thread-safe, stack-based buffer pool (THorseMemoryBufferPool) and a recyclable stream implementation (THorsePooledStream inheriting from TStream).
Instead of allocating new memory:
- Request Body: High-performance providers (like HttpSys and Epoll) acquire a pre-allocated stream buffer from the pool to read and process incoming socket data.
- Response Body: Handlers sending bytes or empty response bodies acquire streams from the pool.
- Automatic Recycling: When a handler finishes processing and calls
.Freeon a pooled stream, the underlyingTBytesbuffer is automatically returned to the global pool for the next request.
This results in a zero-allocation response and request payload mapping for the vast majority of HTTP operations.
For general framework consumers, this optimization is 100% transparent. You do not need to rewrite any route or middleware code to benefit from it. Typical code remains exactly the same:
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong'); // Automatically uses the Memory Buffer Pool under the hood
end);If you are developing custom middlewares or handlers that require heavy streaming or file manipulation, you can voluntarily leverage the memory buffer pool to prevent heap allocation:
uses
Horse,
Horse.Core.MemoryBufferPool,
System.Classes;
begin
THorse.Get('/report',
procedure(Req: THorseRequest; Res: THorseResponse)
var
LStream: TStream;
begin
// Acquire a pre-allocated stream from the global pool (Thread-Safe)
LStream := THorseMemoryBufferPool.DefaultPool.AcquireStream;
try
// Write content into the pooled stream
LStream.WriteBuffer(PChar('Report Data...')^, 14);
// Return the stream. Horse will transmit the data and automatically
// call LStream.Free, returning the buffer to the pool.
Res.SendFile(LStream, 'report.txt');
except
LStream.Free; // Ensure the stream is returned to the pool in case of an exception
raise;
end;
end);
THorse.Listen(9000);
end.By default, the global pool initializes with:
- Standard Buffer Size:
65,536 bytes (64 KB). - Max Pool Stack Size:
1,024inactive buffers kept in RAM. - Max Recyclable Buffer Size:
2,097,152 bytes (2 MB).
Note
If a stream dynamically grows larger than 2 MB (e.g. handling a very large upload), it is discarded upon destruction and a fresh standard 64 KB buffer is created and pushed back to the pool to prevent memory leaks and keep RAM consumption stable.