Skip to content

Instantly share code, notes, and snippets.

@corporatepiyush
Last active October 10, 2025 20:43
Show Gist options
  • Save corporatepiyush/4f86f503e95482cd211388d4e00b8646 to your computer and use it in GitHub Desktop.
Save corporatepiyush/4f86f503e95482cd211388d4e00b8646 to your computer and use it in GitHub Desktop.
High Performace Application level Buffer Pool for Networking, Image Processing, Graphics, Game engines and File IO Tasks
/// @file A high-performance buffer pool implementation in Dart for managing TypedData.
/// @author Piyush Katariya
/// @version 1.5.0
library buffer_pool;
import 'dart:typed_data';
import 'dart:math';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
/// A function signature for creating a new [TypedData] instance of a specific length.
typedef TypedDataCreator<T extends TypedData> = T Function(int length);
/// A function signature for creating a view on a [ByteBuffer].
typedef TypedDataViewCreator<T extends TypedData> =
T Function(ByteBuffer buffer, int offsetInBytes, int length);
/// An enum defining fixed allocation capacities for buffer pool segments.
///
/// These values determine the initial number of segments created within a slab.
enum Capacity {
Initial(1),
Quarter(16),
Half(32),
Distinct(48),
Full(64);
const Capacity(this.value);
final int value;
}
/// Configuration class for buffer pool initialization.
class PoolConfig {
final int minSize;
final int maxSize;
final Capacity capacity;
final List<int>? bucketRange;
final List<int>? segmentsPerBucket;
final List<int>? buffersPerSegment;
const PoolConfig({
this.minSize = 16,
this.maxSize = 256 * 1024,
this.capacity = Capacity.Initial,
this.bucketRange,
this.segmentsPerBucket,
this.buffersPerSegment,
});
/// Validates the configuration parameters.
void validate() {
if (minSize < 16) throw Exception('minSize cannot be less than 16.');
if (maxSize > 16 * 1024 * 1024) {
throw Exception('maxSize cannot be greater than 16 MB.');
}
if (minSize >= maxSize)
throw Exception('minSize must be less than maxSize.');
if (bucketRange != null ||
segmentsPerBucket != null ||
buffersPerSegment != null) {
if (bucketRange == null ||
segmentsPerBucket == null ||
buffersPerSegment == null) {
throw Exception(
'All custom slab configuration arrays must be provided together.',
);
}
if (bucketRange!.length != segmentsPerBucket!.length ||
bucketRange!.length != buffersPerSegment!.length) {
throw Exception(
'All arrays in custom slab config must have the same length.',
);
}
if (bucketRange!.length > BufferPool.maxSlabs) {
throw Exception(
'The length of arrays in custom slab config cannot exceed the maximum of ${BufferPool.maxSlabs}.',
);
}
for (final bucket in bucketRange!) {
if (bucket <= 0) {
throw Exception(
'All bucket sizes in custom slab config must be positive numbers.',
);
}
}
for (int i = 0; i < bucketRange!.length - 1; i++) {
if (bucketRange![i] >= bucketRange![i + 1]) {
throw Exception(
'bucketRange in custom slab config must be sorted in ascending order and contain no duplicate values.',
);
}
}
for (final segmentCount in segmentsPerBucket!) {
if (segmentCount > BufferPool.maxSegments || segmentCount <= 0) {
throw Exception(
'The number of segments per bucket in custom slab config must be between 1 and ${BufferPool.maxSegments}.',
);
}
}
for (final buffersCount in buffersPerSegment!) {
if (buffersCount > BufferPool.maxBuffers || buffersCount <= 0) {
throw Exception(
'The number of buffers per segment in custom slab config must be between 1 and ${BufferPool.maxBuffers}.',
);
}
}
}
}
}
/// Abstract base class for buffers managed by the pool.
abstract class IBuffer<T extends TypedData> {
/// The TypedData view of the buffer's data.
T get data;
/// A packed integer containing metadata for deallocation. Will be 0 for non-pooled buffers.
int get meta;
/// A flag indicating if the buffer is managed by the pool.
bool get isPooled;
}
/// Represents a memory buffer that is a view into a larger slab segment.
///
/// This class uses bit-packing to efficiently store metadata (slab, segment,
/// and buffer indices) within a single 16-bit number, which is crucial for
/// quick deallocation.
class PooledBuffer<T extends TypedData> implements IBuffer<T> {
@override
final T data;
@override
final int meta;
@override
final bool isPooled = true;
// Adjusted masks: slab(4) + segment(6) + buffer(5) = 15 bits
// Slab: bits 11-14 (4 bits for 0-15)
static const int _slabMask = 0x7800; // 0111100000000000
// Segment: bits 5-10 (6 bits for 0-63)
static const int _segmentMask = 0x07E0; // 0000011111100000
// Buffer: bits 0-4 (5 bits for 0-31)
static const int _bufferMask = 0x001F; // 0000000000011111
static const int _slabShift = 11;
static const int _segmentShift = 5;
/// Creates a pooled buffer instance.
PooledBuffer(
int slabIndex,
int segmentIndex,
int bufferIndex,
int elementCount,
T segmentData,
TypedDataViewCreator<T> viewCreator,
) : meta =
(slabIndex << _slabShift) |
(segmentIndex << _segmentShift) |
bufferIndex,
data = viewCreator(
segmentData.buffer,
bufferIndex * elementCount * segmentData.elementSizeInBytes,
elementCount,
);
/// Decodes the slab index from the packed metadata.
@pragma('vm:prefer-inline')
int get slabIndex => (meta & _slabMask) >> _slabShift;
/// Decodes the segment index from the packed metadata.
@pragma('vm:prefer-inline')
int get segmentIndex => (meta & _segmentMask) >> _segmentShift;
/// Decodes the buffer index from the packed metadata.
@pragma('vm:prefer-inline')
int get bufferIndex => meta & _bufferMask;
}
/// Represents a buffer that is not managed by the buffer pool.
///
/// This class follows the null object pattern, with `meta` always as 0,
/// making it easily distinguishable from a [PooledBuffer] during deallocation.
class NonPooledBuffer<T extends TypedData> implements IBuffer<T> {
@override
final T data;
@override
final int meta = 0;
@override
final bool isPooled = false;
/// Creates a non-pooled buffer instance.
NonPooledBuffer(int elementCount, TypedDataCreator<T> creator)
: data = creator(elementCount);
}
/// A Segment is a contiguous block of memory carved into fixed-size buffers.
///
/// Each segment can hold up to 32 buffers of a specific size category (bucket).
class Segment<T extends TypedData> {
final int bucketElementCount;
final T data;
final List<PooledBuffer<T>> bufferPool; // Pre-allocated
final List<int> freeIndices; // Track free buffer indices
int freeCount;
final int slabIndex;
final int segmentIndex;
final int maxBuffers;
Segment(
this.bucketElementCount,
this.slabIndex,
this.segmentIndex,
this.maxBuffers,
TypedDataCreator<T> creator,
TypedDataViewCreator<T> viewCreator,
) : data = creator(bucketElementCount * maxBuffers),
bufferPool = List.generate(
maxBuffers,
(i) => PooledBuffer<T>(
slabIndex,
segmentIndex,
i,
bucketElementCount,
creator(
bucketElementCount * maxBuffers,
), // Temporary, will be replaced
viewCreator,
),
growable: false,
),
freeIndices = List.generate(maxBuffers, (i) => i, growable: false),
freeCount = maxBuffers {
// Re-create buffers with correct data reference
for (int i = 0; i < maxBuffers; i++) {
bufferPool[i] = PooledBuffer<T>(
slabIndex,
segmentIndex,
i,
bucketElementCount,
data,
viewCreator,
);
}
}
/// Allocates a buffer from this segment.
@pragma('vm:prefer-inline')
IBuffer<T> allocate() {
if (freeCount > 0) {
freeCount--;
return bufferPool[freeIndices[freeCount]];
}
return NonPooledBuffer<T>(
bucketElementCount,
(len) => data.buffer.asUint8List(0, 0) as T,
);
}
/// Returns a buffer to this segment's pool.
@pragma('vm:prefer-inline')
void deallocate(int bufferIndex) {
if (freeCount < maxBuffers) {
freeIndices[freeCount] = bufferIndex;
freeCount++;
}
}
}
/// A Slab is a fixed-size buffer pool that manages up to 64 segments.
///
/// Each slab is responsible for a single buffer bucket (a specific size category).
class Slab<T extends TypedData> {
final int bucketElementCount;
final List<Segment<T>> segments;
final Set<int> freeSegmentSet; // O(1) lookup for free segments
final int slabIndex;
final int buffersPerSegment;
final TypedDataCreator<T> _creator;
final TypedDataViewCreator<T> _viewCreator;
Slab(
this.bucketElementCount,
this.slabIndex,
int initialCapacity,
this.buffersPerSegment,
this._creator,
this._viewCreator,
) : segments = [],
freeSegmentSet = {} {
for (int i = 0; i < initialCapacity; i++) {
_addSegment();
}
}
/// Adds a new segment to the slab if capacity allows.
void _addSegment() {
if (segments.length >= BufferPool.maxSegments) {
throw Exception('Maximum number of segments reached for this slab.');
}
final newSegment = Segment<T>(
bucketElementCount,
slabIndex,
segments.length,
buffersPerSegment,
_creator,
_viewCreator,
);
segments.add(newSegment);
freeSegmentSet.add(segments.length - 1);
}
/// Allocates a buffer from one of the slab's segments.
@pragma('vm:prefer-inline')
IBuffer<T> allocate() {
if (freeSegmentSet.isEmpty && segments.length < BufferPool.maxSegments) {
_addSegment();
}
if (freeSegmentSet.isEmpty) {
return NonPooledBuffer<T>(bucketElementCount, _creator);
}
final segmentIndex = freeSegmentSet.first;
final segment = segments[segmentIndex];
final buffer = segment.allocate();
if (segment.freeCount == 0) {
freeSegmentSet.remove(segmentIndex);
}
return buffer;
}
/// Deallocates a buffer, returning it to the appropriate segment.
@pragma('vm:prefer-inline')
void deallocate(PooledBuffer<T> buffer) {
final segmentIndex = buffer.segmentIndex;
if (segmentIndex >= 0 && segmentIndex < segments.length) {
final segment = segments[segmentIndex];
final wasFull = segment.freeCount == 0;
segment.deallocate(buffer.bufferIndex);
if (wasFull) {
freeSegmentSet.add(segmentIndex);
}
}
}
/// The total number of free buffers across all segments in this slab.
int get freeCount =>
segments.fold(0, (total, segment) => total + segment.freeCount);
}
/// Manages multiple slabs, providing a unified interface for allocation and deallocation.
class InternalBufferPool<T extends TypedData> {
final List<Slab<T>> slabs;
final List<int> buckets;
final TypedDataCreator<T> _creator;
InternalBufferPool(
PoolConfig config,
this._creator,
TypedDataViewCreator<T> viewCreator,
) : slabs = [],
buckets = [] {
config.validate();
final int initialCapacity = config.capacity.value;
late List<int> finalBucketRange;
late List<int> finalSegmentsPerBucket;
late List<int> finalBuffersPerSegment;
if (config.bucketRange != null) {
finalBucketRange = config.bucketRange!;
finalSegmentsPerBucket = config.segmentsPerBucket!;
finalBuffersPerSegment = config.buffersPerSegment!;
} else {
finalBucketRange = _generateBetterBuckets(config.minSize, config.maxSize);
finalSegmentsPerBucket = List.filled(
finalBucketRange.length,
initialCapacity,
);
finalBuffersPerSegment = List.filled(
finalBucketRange.length,
BufferPool.maxBuffers,
);
}
buckets.addAll(finalBucketRange);
for (int i = 0; i < buckets.length; i++) {
final bucket = buckets[i];
final segments = finalSegmentsPerBucket[i];
final buffers = finalBuffersPerSegment[i];
final slab = Slab<T>(bucket, i, segments, buffers, _creator, viewCreator);
slabs.add(slab);
}
}
/// Generates better distributed bucket sizes for common use cases
static List<int> _generateBetterBuckets(int minSize, int maxSize) {
final sizes = <int>{};
// Add power-of-2 sizes
for (int power = 4; power <= 20; power++) {
final base = 1 << power;
if (base >= minSize && base <= maxSize) {
sizes.add(base);
}
}
// Add common small sizes
for (int size = minSize; size <= min(256, maxSize); size += 16) {
sizes.add(size);
}
// Ensure we have the min and max
sizes.add(minSize);
sizes.add(maxSize);
final result = sizes.toList()..sort();
// Limit to maxSlabs (16)
if (result.length > BufferPool.maxSlabs) {
final step = result.length / BufferPool.maxSlabs;
final filtered = <int>[];
for (int i = 0; i < BufferPool.maxSlabs; i++) {
filtered.add(result[(i * step).floor()]);
}
return filtered;
}
return result;
}
/// Acquires a buffer that can hold at least the requested number of elements.
@pragma('vm:prefer-inline')
IBuffer<T> acquire(int requestedElementCount) {
// Binary search for the appropriate bucket
int low = 0;
int high = buckets.length - 1;
while (low <= high) {
final mid = (low + high) >>> 1;
final currentBucket = buckets[mid];
if (currentBucket >= requestedElementCount) {
if (mid == 0 || buckets[mid - 1] < requestedElementCount) {
return slabs[mid].allocate();
}
high = mid - 1;
} else {
low = mid + 1;
}
}
return NonPooledBuffer<T>(requestedElementCount, _creator);
}
/// Acquires a buffer and ensures its contents are zeroed out.
IBuffer<T> acquireSafe(int requestedElementCount) {
final buffer = acquire(requestedElementCount);
if (buffer.isPooled) {
final data = buffer.data;
// Use fillRange which is optimized in Dart VM
if (data is Uint8List) {
data.fillRange(0, data.length, 0);
} else if (data is Int8List) {
data.fillRange(0, data.length, 0);
} else if (data is Uint16List) {
data.fillRange(0, data.length, 0);
} else if (data is Int16List) {
data.fillRange(0, data.length, 0);
} else if (data is Uint32List) {
data.fillRange(0, data.length, 0);
} else if (data is Int32List) {
data.fillRange(0, data.length, 0);
} else if (data is Float32List) {
data.fillRange(0, data.length, 0.0);
} else if (data is Float64List) {
data.fillRange(0, data.length, 0.0);
} else {
final byteData = data.buffer.asUint8List(
data.offsetInBytes,
data.lengthInBytes,
);
byteData.fillRange(0, byteData.length, 0);
}
}
return buffer;
}
/// Releases a buffer back to the pool.
@pragma('vm:prefer-inline')
void release(IBuffer<T> buffer) {
if (buffer.isPooled && buffer is PooledBuffer<T>) {
final slabIndex = buffer.slabIndex;
if (slabIndex >= 0 && slabIndex < slabs.length) {
slabs[slabIndex].deallocate(buffer);
}
}
}
/// The total number of free buffers across all slabs.
int get freeCount => slabs.fold(0, (total, slab) => total + slab.freeCount);
}
/// The main public-facing class for the buffer pool.
class BufferPool {
/// Maximum number of slabs (bucket sizes) the pool can manage.
static const int maxSlabs = 16;
/// Maximum number of segments a single slab can manage.
static const int maxSegments = 64;
/// Maximum number of buffers a single segment can hold.
static const int maxBuffers = 32;
static final Map<Type, TypedDataCreator> _creatorRegistry = {
Int8List: (int length) => Int8List(length),
Uint8List: (int length) => Uint8List(length),
Int16List: (int length) => Int16List(length),
Uint16List: (int length) => Uint16List(length),
Int32List: (int length) => Int32List(length),
Uint32List: (int length) => Uint32List(length),
Float32List: (int length) => Float32List(length),
Float64List: (int length) => Float64List(length),
};
static final Map<Type, TypedDataCreator> _heapCreatorRegistry = {
Int8List: (int length) => malloc<Int8>(length).asTypedList(length),
Uint8List: (int length) => malloc<Uint8>(length).asTypedList(length),
Int16List: (int length) => malloc<Int16>(length).asTypedList(length),
Uint16List: (int length) => malloc<Uint16>(length).asTypedList(length),
Int32List: (int length) => malloc<Int32>(length).asTypedList(length),
Uint32List: (int length) => malloc<Uint32>(length).asTypedList(length),
Float32List: (int length) => malloc<Float>(length).asTypedList(length),
Float64List: (int length) => malloc<Double>(length).asTypedList(length),
};
static final Map<Type, TypedDataViewCreator> _viewCreatorRegistry = {
Int8List: (ByteBuffer b, int o, int l) => b.asInt8List(o, l),
Uint8List: (ByteBuffer b, int o, int l) => b.asUint8List(o, l),
Int16List: (ByteBuffer b, int o, int l) => b.asInt16List(o, l),
Uint16List: (ByteBuffer b, int o, int l) => b.asUint16List(o, l),
Int32List: (ByteBuffer b, int o, int l) => b.asInt32List(o, l),
Uint32List: (ByteBuffer b, int o, int l) => b.asUint32List(o, l),
Float32List: (ByteBuffer b, int o, int l) => b.asFloat32List(o, l),
Float64List: (ByteBuffer b, int o, int l) => b.asFloat64List(o, l),
};
/// Provides a managed scope for using a buffer pool instance.
static void usingPool<T extends TypedData>({
PoolConfig? config,
bool offHeap = false,
required void Function(InternalBufferPool<T>) fn,
}) {
final creator =
(offHeap ? _heapCreatorRegistry[T] : _creatorRegistry[T])
as TypedDataCreator<T>?;
final viewCreator = _viewCreatorRegistry[T] as TypedDataViewCreator<T>?;
if (creator == null || viewCreator == null) {
throw Exception(
'Unsupported TypedArray type: $T. '
'Please ensure it is one of the standard `TypedData` types.',
);
}
final finalConfig = config ?? const PoolConfig();
final bufferPool = InternalBufferPool<T>(finalConfig, creator, viewCreator);
fn(bufferPool);
}
}
// --- Benchmark Configuration ---
@pragma('vm:prefer-inline')
void touchEveryElement(TypedData buffer) {
// unique bit-pattern for each type
if (buffer is Uint8List) {
var sum = 0;
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = (i & 0xFF); // 0 … 255
sum += buffer[i];
}
// prevent dead-code elimination
if (sum == 0xDEADBEEF) print(sum);
} else if (buffer is Int8List) {
var sum = 0;
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = ((i & 0x7F) - 64); // -64 … 63
sum += buffer[i];
}
if (sum == 0xDEADBEEF) print(sum);
} else if (buffer is Uint16List) {
var sum = 0;
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = (i & 0xFFFF); // 0 … 65 535
sum += buffer[i];
}
if (sum == 0xDEADBEEF) print(sum);
} else if (buffer is Int16List) {
var sum = 0;
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = ((i & 0x7FFF) - 16000); // -16000 … 32 767
sum += buffer[i];
}
if (sum == 0xDEADBEEF) print(sum);
} else if (buffer is Uint32List) {
var sum = 0;
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = (0xAAAAAAAA + i); // big unsigned
sum += buffer[i];
}
if (sum == 0xDEADBEEF) print(sum);
} else if (buffer is Int32List) {
var sum = 0;
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = (0x7FFFFFF0 - i); // big positive
sum += buffer[i];
}
if (sum == 0xDEADBEEF) print(sum);
} else if (buffer is Float32List) {
var sum = 0.0;
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = (i * 1.234).toDouble(); // float pattern
sum += buffer[i];
}
if (sum == 0xDEADBEEF) print(sum);
} else if (buffer is Float64List) {
var sum = 0.0;
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = (i * 3.141592653589793); // double pattern
sum += buffer[i];
}
if (sum == 0xDEADBEEF) print(sum);
}
}
/// A generic function to run a full benchmark suite for a given TypedData type.
Future<void> runBenchmarkForType<T extends TypedData>({
required String name,
PoolConfig? config,
bool offHeap = false,
}) async {
final random = Random();
const warmupOps = 100;
const benchmarkOps = 10000;
final bufferSizes = [
128,
256,
512,
1024,
2048,
4096,
8192,
16384,
32768,
65536,
131072,
];
final warmupIndices = List<int>.generate(
warmupOps,
(_) => bufferSizes[random.nextInt(bufferSizes.length)],
);
final benchmarkIndices = List<int>.generate(
benchmarkOps,
(_) => bufferSizes[random.nextInt(bufferSizes.length)],
);
print('\n============================================================');
print('PERFORMANCE BENCHMARK FOR: $name');
print('============================================================');
print('Configuration:');
print(' - Warm-up Operations: $warmupOps');
print(' - Benchmark Operations: $benchmarkOps');
print(' - Buffer Sizes: $bufferSizes');
print(
' - Pool Capacity: ${config?.capacity.name ?? "Initial"}\n',
);
BufferPool.usingPool<T>(
config: config,
offHeap: offHeap,
fn: (pool) {
// Warm-up
print('PHASE 1: WARM-UP');
for (final size in warmupIndices) {
final buffer = pool.acquire(size);
pool.release(buffer);
}
print('Warm-up complete.\n');
void measure(String label, void Function() fn) {
final sw = Stopwatch()..start();
fn();
sw.stop();
final timeMs = sw.elapsedMilliseconds;
final opsPerSec = timeMs > 0
? (benchmarkOps * 1000 ~/ timeMs)
: benchmarkOps * 1000;
print(
'Test: $label | Time: ${timeMs.toString().padLeft(6)} ms | '
'Ops/sec: ~${opsPerSec.toString().padLeft(10)}',
);
}
measure("Pooled acquire/release", () {
for (final size in benchmarkIndices) {
final buffer = pool.acquire(size);
touchEveryElement(buffer.data);
pool.release(buffer);
}
});
measure("Pooled safe acquire/release", () {
for (final size in benchmarkIndices) {
final buffer = pool.acquireSafe(size);
touchEveryElement(buffer.data);
pool.release(buffer);
}
});
measure("Control (Non-Pooled)", () {
for (final size in benchmarkIndices) {
T buffer;
if (T == Uint8List) {
buffer = Uint8List(size) as T;
} else if (T == Uint16List) {
buffer = Uint16List(size) as T;
} else if (T == Int32List) {
buffer = Int32List(size) as T;
} else if (T == Float32List) {
buffer = Float32List(size) as T;
} else if (T == Float64List) {
buffer = Float64List(size) as T;
} else {
throw UnsupportedError("Type not supported");
}
touchEveryElement(buffer);
}
});
},
);
print('============================================================\n');
}
void main() {
final config = PoolConfig(
minSize: 128,
maxSize: 131072,
capacity: Capacity.Full,
);
runBenchmarkForType<Uint8List>(name: 'Uint8List', config: config);
runBenchmarkForType<Uint8List>(
name: 'Uint8List Off-Heap',
config: config,
offHeap: true,
);
runBenchmarkForType<Uint16List>(name: 'Uint16List', config: config);
runBenchmarkForType<Uint16List>(
name: 'Uint16List Off-Heap',
config: config,
offHeap: true,
);
runBenchmarkForType<Int32List>(name: 'Int32List', config: config);
runBenchmarkForType<Int32List>(
name: 'Int32List Off-Heap',
config: config,
offHeap: true,
);
runBenchmarkForType<Float32List>(name: 'Float32List', config: config);
runBenchmarkForType<Float32List>(
name: 'Float32List Off-Heap',
config: config,
offHeap: true,
);
runBenchmarkForType<Float64List>(name: 'Float64List', config: config);
runBenchmarkForType<Float64List>(
name: 'Float64List Off-Heap',
config: config,
offHeap: true,
);
}
/**
* @file A high-performance buffer pool implementation in JavaScript for managing TypedArrays, with examples
* @author Piyush Katariya
* @version 1.3.0
* @description Works in both Browser and Node.js environments
*/
/**
* An enum defining fixed allocation capacities for buffer pool segments.
*/
const Capacity = Object.freeze({
Initial: 1,
Quarter: 16,
Half: 32,
Distinct: 48,
Full: 64
});
/**
* Configuration class for buffer pool initialization.
*/
class PoolConfig {
constructor({
minSize = 16,
maxSize = 256 * 1024,
capacity = Capacity.Initial,
bucketRange = null,
segmentsPerBucket = null,
buffersPerSegment = null,
enableAdaptiveGrowth = true
} = {}) {
this.minSize = minSize;
this.maxSize = maxSize;
this.capacity = capacity;
this.bucketRange = bucketRange;
this.segmentsPerBucket = segmentsPerBucket;
this.buffersPerSegment = buffersPerSegment;
this.enableAdaptiveGrowth = enableAdaptiveGrowth;
}
/**
* Validates the configuration parameters.
*/
validate() {
if (this.minSize < 16) throw new Error('minSize cannot be less than 16.');
if (this.maxSize > 16 * 1024 * 1024) {
throw new Error('maxSize cannot be greater than 16 MB.');
}
if (this.minSize >= this.maxSize) throw new Error('minSize must be less than maxSize.');
if (this.bucketRange !== null || this.segmentsPerBucket !== null || this.buffersPerSegment !== null) {
if (this.bucketRange === null || this.segmentsPerBucket === null || this.buffersPerSegment === null) {
throw new Error('All custom slab configuration arrays must be provided together.');
}
if (this.bucketRange.length !== this.segmentsPerBucket.length ||
this.bucketRange.length !== this.buffersPerSegment.length) {
throw new Error('All arrays in custom slab config must have the same length.');
}
if (this.bucketRange.length > BufferPool.maxSlabs) {
throw new Error(
`The length of arrays in custom slab config cannot exceed the maximum of ${BufferPool.maxSlabs}.`);
}
for (const bucket of this.bucketRange) {
if (bucket <= 0) {
throw new Error('All bucket sizes in custom slab config must be positive numbers.');
}
}
for (let i = 0; i < this.bucketRange.length - 1; i++) {
if (this.bucketRange[i] >= this.bucketRange[i + 1]) {
throw new Error(
'bucketRange in custom slab config must be sorted in ascending order and contain no duplicate values.');
}
}
for (const segmentCount of this.segmentsPerBucket) {
if (segmentCount > BufferPool.maxSegments || segmentCount <= 0) {
throw new Error(
`The number of segments per bucket in custom slab config must be between 1 and ${BufferPool.maxSegments}.`);
}
}
for (const buffersCount of this.buffersPerSegment) {
if (buffersCount > BufferPool.maxBuffers || buffersCount <= 0) {
throw new Error(
`The number of buffers per segment in custom slab config must be between 1 and ${BufferPool.maxBuffers}.`);
}
}
}
}
}
/**
* Abstract base class for buffers managed by the pool.
*/
class IBuffer {
constructor() {
this.data = null;
this.meta = 0;
this.isPooled = false;
}
}
/**
* Represents a memory buffer that is a view into a larger slab segment.
*/
class PooledBuffer extends IBuffer {
// Adjusted masks: slab(4) + segment(6) + buffer(5) = 15 bits
static _slabMask = 0x7800; // 0111100000000000
static _segmentMask = 0x07E0; // 0000011111100000
static _bufferMask = 0x001F; // 0000000000011111
static _slabShift = 11;
static _segmentShift = 5;
constructor(slabIndex, segmentIndex, bufferIndex, elementCount, segmentData, viewCreator) {
super();
this.meta = (slabIndex << PooledBuffer._slabShift) |
(segmentIndex << PooledBuffer._segmentShift) |
bufferIndex;
const bytesPerElement = segmentData.BYTES_PER_ELEMENT;
const byteOffset = bufferIndex * elementCount * bytesPerElement;
this.data = viewCreator(segmentData.buffer, byteOffset, elementCount);
this.isPooled = true;
}
get slabIndex() {
return (this.meta & PooledBuffer._slabMask) >> PooledBuffer._slabShift;
}
get segmentIndex() {
return (this.meta & PooledBuffer._segmentMask) >> PooledBuffer._segmentShift;
}
get bufferIndex() {
return this.meta & PooledBuffer._bufferMask;
}
}
/**
* Represents a buffer that is not managed by the buffer pool.
*/
class NonPooledBuffer extends IBuffer {
constructor(elementCount, creator) {
super();
this.data = creator(elementCount);
this.meta = 0;
this.isPooled = false;
}
}
/**
* A Segment is a contiguous block of memory carved into fixed-size buffers.
*/
class Segment {
constructor(bucketElementCount, slabIndex, segmentIndex, maxBuffers, creator, viewCreator) {
this.bucketElementCount = bucketElementCount;
this.slabIndex = slabIndex;
this.segmentIndex = segmentIndex;
this.maxBuffers = maxBuffers;
this._creator = creator;
this._viewCreator = viewCreator;
this.nextFreeBufferIndex = 0;
this.reset();
}
reset() {
const totalElements = this.bucketElementCount * this.maxBuffers;
this.data = this._creator(totalElements);
this.localPooledBuffers = new Array(this.maxBuffers).fill(null);
this._initializeSegment();
}
_initializeSegment() {
for (let i = 0; i < this.maxBuffers; i++) {
const buffer = new PooledBuffer(
this.slabIndex, this.segmentIndex, i, this.bucketElementCount,
this.data, this._viewCreator);
this.localPooledBuffers[i] = buffer;
}
this.nextFreeBufferIndex = this.maxBuffers - 1;
}
allocate() {
if (this.nextFreeBufferIndex >= 0) {
const buffer = this.localPooledBuffers[this.nextFreeBufferIndex];
this.localPooledBuffers[this.nextFreeBufferIndex] = null;
this.nextFreeBufferIndex--;
return buffer;
}
return new NonPooledBuffer(this.bucketElementCount, this._creator);
}
deallocate(buffer) {
if (buffer instanceof PooledBuffer) {
if (this.nextFreeBufferIndex < this.maxBuffers - 1) {
this.nextFreeBufferIndex++;
this.localPooledBuffers[this.nextFreeBufferIndex] = buffer;
} else {
console.warn("WARN: Attempting to deallocate a buffer to a full segment. This should not happen.");
}
}
}
get freeCount() {
return this.nextFreeBufferIndex + 1;
}
}
/**
* A Slab is a fixed-size buffer pool that manages up to 64 segments.
*/
class Slab {
constructor(bucketElementCount, slabIndex, initialCapacity, buffersPerSegment, creator, viewCreator) {
this.bucketElementCount = bucketElementCount;
this.slabIndex = slabIndex;
this.initialCapacity = initialCapacity;
this.buffersPerSegment = buffersPerSegment;
this._creator = creator;
this._viewCreator = viewCreator;
this.segments = [];
this.freeSegmentIndices = [];
this._allocationAttempts = 0;
this.reset();
}
reset() {
this.segments.length = 0;
this.freeSegmentIndices.length = 0;
for (let i = 0; i < this.initialCapacity; i++) {
this.addSegment();
}
}
addSegment() {
if (this.segments.length >= BufferPool.maxSegments) {
throw new Error('Maximum number of segments reached for this slab.');
}
const newSegment = new Segment(
this.bucketElementCount,
this.slabIndex,
this.segments.length,
this.buffersPerSegment,
this._creator,
this._viewCreator
);
this.segments.push(newSegment);
this.freeSegmentIndices.push(this.segments.length - 1);
}
allocate() {
let segmentIndex = null;
if (this.freeSegmentIndices.length > 0) {
segmentIndex = this.freeSegmentIndices.pop();
}
if (segmentIndex === null) {
if (this.segments.length < BufferPool.maxSegments) {
this.addSegment();
segmentIndex = this.segments.length - 1;
this.freeSegmentIndices.pop();
} else {
if (this._allocationAttempts % 1000 === 0) {
console.warn(
`WARN: All segments in slab for bucket element count ${this.bucketElementCount} are full. Returning unmanaged buffers.`);
}
this._allocationAttempts++;
return new NonPooledBuffer(this.bucketElementCount, this._creator);
}
}
const segment = this.segments[segmentIndex];
const buffer = segment.allocate();
if (segment.freeCount > 0) {
this.freeSegmentIndices.push(segmentIndex);
}
return buffer;
}
deallocate(buffer) {
if (!(buffer instanceof PooledBuffer)) return;
if (buffer.slabIndex !== this.slabIndex) {
console.error(
`ERROR: Deallocation Error: Buffer's slab index (${buffer.slabIndex}) does not match current slab's index (${this.slabIndex}).`);
return;
}
const segmentIndex = buffer.segmentIndex;
if (segmentIndex >= 0 && segmentIndex < this.segments.length) {
const wasFull = this.segments[segmentIndex].freeCount === 0;
this.segments[segmentIndex].deallocate(buffer);
if (wasFull) {
this.freeSegmentIndices.push(segmentIndex);
}
} else {
console.error(
`ERROR: Deallocation Error: Buffer's segment index (${segmentIndex}) is out of bounds for this slab.`);
}
}
get freeCount() {
return this.segments.reduce((total, segment) => total + segment.freeCount, 0);
}
}
/**
* Manages multiple slabs, providing a unified interface for allocation and deallocation.
*/
class InternalBufferPool {
constructor(config, creator, viewCreator) {
config.validate();
this.slabs = [];
this.buckets = [];
this.bucketMap = {};
this._creator = creator;
this._viewCreator = viewCreator;
this._fallbackWarningCount = 0;
const initialCapacity = config.capacity;
let finalBucketRange, finalSegmentsPerBucket, finalBuffersPerSegment;
if (config.bucketRange !== null) {
finalBucketRange = config.bucketRange;
finalSegmentsPerBucket = config.segmentsPerBucket;
finalBuffersPerSegment = config.buffersPerSegment;
} else {
finalBucketRange = InternalBufferPool._generateBetterBuckets(config.minSize, config.maxSize);
finalSegmentsPerBucket = new Array(finalBucketRange.length).fill(initialCapacity);
finalBuffersPerSegment = new Array(finalBucketRange.length).fill(BufferPool.maxBuffers);
}
this.buckets = finalBucketRange;
for (let i = 0; i < this.buckets.length; i++) {
const bucket = this.buckets[i];
const segments = finalSegmentsPerBucket[i];
const buffers = finalBuffersPerSegment[i];
const slab = new Slab(bucket, i, segments, buffers, creator, viewCreator);
this.slabs.push(slab);
this.bucketMap[bucket] = i;
}
}
static _generateBetterBuckets(minSize, maxSize) {
const sizes = new Set();
// Add power-of-2 sizes and their multiples
for (let power = 4; power <= 20; power++) {
const base = 1 << power; // 2^power
if (base >= minSize && base <= maxSize) {
sizes.add(base);
// Add 1.5x multiples for better coverage
const mult = Math.round(base * 1.5);
if (mult <= maxSize) sizes.add(mult);
}
}
// Add common small sizes
for (let size = minSize; size <= Math.min(128, maxSize); size += 8) {
sizes.add(size);
}
// Ensure we have the min and max
sizes.add(minSize);
sizes.add(maxSize);
const result = Array.from(sizes).sort((a, b) => a - b);
// Limit to maxSlabs (16)
if (result.length > BufferPool.maxSlabs) {
// Keep evenly distributed sizes
const step = result.length / BufferPool.maxSlabs;
const filtered = [];
for (let i = 0; i < BufferPool.maxSlabs; i++) {
filtered.push(result[Math.floor(i * step)]);
}
return filtered;
}
return result;
}
reset() {
for (const slab of this.slabs) {
slab.reset();
}
}
acquire(requestedElementCount) {
let low = 0;
let high = this.buckets.length - 1;
let slabIndex = -1;
while (low <= high) {
const mid = Math.floor(low + (high - low) / 2);
const currentBucket = this.buckets[mid];
if (currentBucket >= requestedElementCount) {
slabIndex = mid;
high = mid - 1;
} else {
low = mid + 1;
}
}
if (slabIndex !== -1) {
const slab = this.slabs[slabIndex];
return slab.allocate();
}
if (this._fallbackWarningCount < 5) {
console.warn(
`WARN: No suitable slab found for requested count of ${requestedElementCount}. Returning a new, unmanaged buffer.`);
this._fallbackWarningCount++;
if (this._fallbackWarningCount === 5) {
console.warn('WARN: Suppressing further fallback warnings...');
}
}
return new NonPooledBuffer(requestedElementCount, this._creator);
}
acquireSafe(requestedElementCount) {
const buffer = this.acquire(requestedElementCount);
if (buffer.isPooled) {
// Clear the entire buffer data
const view = new Uint8Array(buffer.data.buffer, buffer.data.byteOffset, buffer.data.byteLength);
view.fill(0);
}
return buffer;
}
release(buffer) {
if (buffer.isPooled) {
const pooledBuffer = buffer;
const slabIndex = pooledBuffer.slabIndex;
if (slabIndex >= 0 && slabIndex < this.slabs.length) {
const slab = this.slabs[slabIndex];
slab.deallocate(pooledBuffer);
} else {
console.error('ERROR: No matching slab found for buffer. Cannot deallocate.');
}
}
// Silently ignore unmanaged buffers
}
get freeCount() {
return this.slabs.reduce((total, slab) => total + slab.freeCount, 0);
}
}
/**
* The main public-facing class for the buffer pool.
*/
class BufferPool {
static maxSlabs = 16;
static maxSegments = 64;
static maxBuffers = 32;
static _creatorRegistry = {
Int8Array: (length) => new Int8Array(length),
Uint8Array: (length) => new Uint8Array(length),
Int16Array: (length) => new Int16Array(length),
Uint16Array: (length) => new Uint16Array(length),
Int32Array: (length) => new Int32Array(length),
Uint32Array: (length) => new Uint32Array(length),
Float32Array: (length) => new Float32Array(length),
Float64Array: (length) => new Float64Array(length),
};
static _viewCreatorRegistry = {
Int8Array: (buffer, offset, length) => new Int8Array(buffer, offset, length),
Uint8Array: (buffer, offset, length) => new Uint8Array(buffer, offset, length),
Int16Array: (buffer, offset, length) => new Int16Array(buffer, offset, length),
Uint16Array: (buffer, offset, length) => new Uint16Array(buffer, offset, length),
Int32Array: (buffer, offset, length) => new Int32Array(buffer, offset, length),
Uint32Array: (buffer, offset, length) => new Uint32Array(buffer, offset, length),
Float32Array: (buffer, offset, length) => new Float32Array(buffer, offset, length),
Float64Array: (buffer, offset, length) => new Float64Array(buffer, offset, length),
};
/**
* Provides a managed scope for using a buffer pool instance.
* @param {string} arrayType - The TypedArray type ('Float32Array', 'Uint8Array', etc.)
* @param {PoolConfig} config - Pool configuration
* @param {Function} fn - Function to execute with the pool
*/
static usingPool(arrayType, config = null, fn) {
if (typeof config === 'function') {
fn = config;
config = null;
}
const creator = this._creatorRegistry[arrayType];
const viewCreator = this._viewCreatorRegistry[arrayType];
if (!creator || !viewCreator) {
throw new Error(`Unsupported TypedArray type: ${arrayType}. ` +
'Please ensure it is one of the standard TypedArray types.');
}
const finalConfig = config || new PoolConfig();
const bufferPool = new InternalBufferPool(finalConfig, creator, viewCreator);
return fn(bufferPool);
}
}
// --- Examples ---
function runLoadTestExample() {
console.log("\n--- Running Load Test Example ---");
const numAllocations = 20000;
const minSize = 16;
const maxSize = 100;
BufferPool.usingPool('Float32Array', new PoolConfig({ minSize, maxSize, capacity: Capacity.Full }), (pool) => {
let unmanagedCount = 0;
let pooledCount = 0;
const allocatedBuffers = [];
console.log(`Starting ${numAllocations} allocations and deallocations...`);
// Stage 1: Allocate a large number of buffers of random sizes.
for (let i = 0; i < numAllocations; i++) {
const bufferSize = minSize + Math.floor(Math.random() * (maxSize - minSize + 1));
const buffer = pool.acquire(bufferSize);
allocatedBuffers.push(buffer);
if (buffer.isPooled) {
pooledCount++;
} else {
unmanagedCount++;
}
}
console.log(`\nInitial allocations complete. Total buffers acquired: ${allocatedBuffers.length}`);
console.log(`Pooled buffers: ${pooledCount}`);
console.log(`Unmanaged buffers: ${unmanagedCount}`);
console.log(`Current free buffers in pool: ${pool.freeCount}`);
// Stage 2: Deallocate all the buffers.
console.log("\nReleasing all allocated buffers back to the pool...");
for (const buffer of allocatedBuffers) {
pool.release(buffer);
}
console.log(`Final free buffers in pool: ${pool.freeCount}`);
console.log("Deallocation complete. The pool should now be mostly full again.");
});
}
function runEfficiencyExample() {
console.log("\n\n--- Running Sequential vs. Interleaved Efficiency Example ---");
// Scenario 1: Sequential Allocation and Deallocation
console.log("\nScenario 1: Acquire all buffers, then release all buffers.");
BufferPool.usingPool('Uint8Array', new PoolConfig({ minSize: 16, maxSize: 16000, capacity: Capacity.Initial }), (pool) => {
const buffers = [];
console.log(`Initial free count: ${pool.freeCount}`);
// Acquire all
for (let i = 0; i < 50; i++) {
buffers.push(pool.acquire(20));
}
console.log(`After acquiring 50 buffers: free count is ${pool.freeCount}`);
// Release all
for (const buffer of buffers) {
pool.release(buffer);
}
console.log(`After releasing 50 buffers: free count is ${pool.freeCount}`);
});
// Scenario 2: Interleaved Allocation and Deallocation
console.log("\nScenario 2: Acquire and release buffers one by one.");
BufferPool.usingPool('Uint8Array', new PoolConfig({ minSize: 16, maxSize: 16000, capacity: Capacity.Initial }), (pool) => {
console.log(`Initial free count: ${pool.freeCount}`);
// Acquire and release in a loop
for (let i = 0; i < 50; i++) {
const buffer = pool.acquire(20);
pool.release(buffer);
}
console.log(`After 50 acquire/release cycles: free count is ${pool.freeCount}`);
console.log("Notice how the free count remains stable, demonstrating memory reuse.");
});
}
function runMixedTypeExample() {
console.log("\n\n--- Running Mixed Type Example ---");
console.log("\nUsing pool with Float32Array...");
BufferPool.usingPool('Float32Array', (pool) => {
const buffer1 = pool.acquire(32);
console.log(`Acquired a Float32Array of length ${buffer1.data.length}`);
pool.release(buffer1);
console.log(`Released Float32Array. Free count: ${pool.freeCount}`);
});
console.log("\nUsing pool with Int16Array...");
BufferPool.usingPool('Int16Array', (pool) => {
const buffer2 = pool.acquire(64);
console.log(`Acquired an Int16Array of length ${buffer2.data.length}`);
pool.release(buffer2);
console.log(`Released Int16Array. Free count: ${pool.freeCount}`);
});
}
function runSlabExhaustionExample() {
console.log("\n\n--- Running Slab Exhaustion Example ---");
const bufferSize = 20;
const initialBufferCount = BufferPool.maxBuffers;
BufferPool.usingPool('Int32Array', new PoolConfig({ minSize: 16, maxSize: 100, capacity: Capacity.Initial }), (pool) => {
const allocated = [];
console.log(`Initial free buffers for size ${bufferSize}: ${pool.freeCount}`);
console.log(`\nAllocating ${initialBufferCount} buffers to exhaust the initial segment...`);
for (let i = 0; i < initialBufferCount; i++) {
allocated.push(pool.acquire(bufferSize));
}
console.log(`Free count after exhausting initial segment: ${pool.freeCount}`);
console.log("\nAllocating one more buffer to force a new segment to be created...");
const extraBuffer = pool.acquire(bufferSize);
allocated.push(extraBuffer);
console.log(`Free count after allocating a new segment: ${pool.freeCount}`);
console.log("A new segment was added to the slab for this buffer size.");
// Clean up
for (const buffer of allocated) {
pool.release(buffer);
}
console.log(`\nFinal free count after releasing all buffers: ${pool.freeCount}`);
});
}
function runSafeAcquireExample() {
console.log("\n\n--- Running Safe Acquire Example ---");
BufferPool.usingPool('Uint8Array', (pool) => {
console.log("Acquiring a buffer and filling it with data...");
const buffer1 = pool.acquire(16);
if (buffer1 instanceof PooledBuffer) {
buffer1.data.fill(99, 0, 16);
console.log(`Buffer 1 data (first 4 elements): [${Array.from(buffer1.data.subarray(0, 4))}]`);
}
pool.release(buffer1);
console.log("Buffer 1 released.");
console.log("\nAcquiring a buffer of the same size with `acquire`...");
const buffer2 = pool.acquire(16);
if (buffer2 instanceof PooledBuffer) {
console.log(`Buffer 2 data (first 4 elements): [${Array.from(buffer2.data.subarray(0, 4))}]`);
console.log("Note: The data from the previous buffer is still present.");
}
pool.release(buffer2);
console.log("\nAcquiring a buffer of the same size with `acquireSafe`...");
const buffer3 = pool.acquireSafe(16);
if (buffer3 instanceof PooledBuffer) {
console.log(`Buffer 3 data (first 4 elements): [${Array.from(buffer3.data.subarray(0, 4))}]`);
console.log("Note: The data has been zeroed out for security.");
}
pool.release(buffer3);
});
}
// Main execution function
function main() {
runLoadTestExample();
runEfficiencyExample();
runMixedTypeExample();
runSlabExhaustionExample();
runSafeAcquireExample();
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment