@bnaya/malloc-temporary-fork
v4.0.11-transferable
Published
ArrayBuffer based malloc() impl for hybrid JS/WASM use cases, based on thi.ng/tinyalloc
Downloads
5
Readme
@thi.ng/malloc
This project is part of the @thi.ng/umbrella monorepo.
About
TypeScript port of
thi.ng/tinyalloc, for raw or
typed array memory pooling and/or hybrid JS/WASM use cases etc. Supports
free block compaction and configurable splitting. Unlike the original,
this implementation also supports realloc()
and does not constrain the
overall number of blocks in use and the only imposed limit is that of
the underlying array buffer.
Each MemPool
instance operates on a single large ArrayBuffer
used as
backing memory chunk, e.g. the same buffer used by a WASM module.
Even for non-WASM use cases, using this package can drastically speed up allocation of typed arrays and reduce GC pressure. See benchmarks below.
Installation
yarn add @thi.ng/malloc
Dependencies
Usage examples
import { Type } from "@thi.ng/api";
import { MemPool } from "@thi.ng/malloc";
// create memory w/ optional start allocation address
// (start address can't be zero, reserved for malloc/calloc failure)
const pool = new MemPool({ size: 0x1000, start: 8 });
// all memory blocks will be aligned to 8-byte boundaries
// size is given in bytes
// returns pointer / index in array buffer
ptr = pool.malloc(16);
// 8
// request memory and return as typed view
// in this case we use number of elements, NOT bytes
vec = pool.mallocAs(Type.F64, 4);
// Float64Array [ 0, 0, 0, 0 ]
// report block stats (sizes & addresses in bytes)
pool.stats();
// { free: { count: 0, size: 0 },
// used: { count: 2, size: 48 },
// top: 56,
// available: 4040,
// total: 4096 }
// reallocate/resize block for pointer/address
// (might move block contents to new mem region)
ptr = pool.realloc(ptr, 32);
// same but for arrays created with `mallocAs()`
vec = pool.reallocArray(vec, 5);
// release address or view back into pool / heap
pool.free(ptr);
// true
pool.free(vec);
// true
// pointers / views can only be freed once
pool.free(vec);
// false
// memory blocks are re-merged whenever possible
// likewise, available free blocks might be split
// if smaller size requested...
pool.stats();
// { free: { count: 1, size: 48 },
// used: { count: 0, size: 0 },
// top: 56,
// available: 4040,
// total: 4096 }
API
MemPool
The MemPool
constructor takes an ArrayBuffer
(or size only) and an
optional MemPoolOpts
object for specifying start and end addresses
(byte offsets) delineating the allocatable / managed region and other
options.
// example with default options shown
new MemPool(0x1000, {
start: 0x8,
end: 0x1000,
compact: true,
split: true,
minSplit: 16
});
The default start
address is 8 and end
the length of the buffer. This
start address is also the minimum supported address for memory blocks.
Address 0x0 is reserved as return value for allocation errors.
The compact
option enables recursive compaction / joining of
neighboring free blocks. Enabled by default to minimize fragmentation.
The split
option is used to enable (default) splitting of a larger
suitable free block when allocating a smaller size. minSplit
specifies
the minimum excess between requested size and actual block size, i.e. by
default a block will be split during allocation if there're at least 16
bytes left over. The given value should always be a multiple of 8.
malloc(size: number)
Attempts to allocate a new block of memory of given byte size and
returns start address if successful, or zero (0
) if unsuccessful.
Memory blocks always start at multiples of 8.
mallocAs(type: Type, num: number)
Similar to malloc()
, but returns a typed array view of desired type
and instead of byte size, expects number of elements. Returns null
, if
allocation failed.
Types are referred to via the Type
enum in the base
@thi.ng/api
package, e.g. Type.F64
:
U8
, U8C
, I8
, U16
, I16
, U32
, I32
, F32
, F64
calloc(size: number)
Like malloc()
but zeroes allocated block before returning. Unless the
allocated block is immediately filled with user data, this method is
preferred over malloc()
.
callocAs(type: Type, num: number)
Like mallocAs()
but zeroes allocated block before returning.
realloc(addr: number, size: number)
Attempts to adjust the size of the block at the given allocated address
to new size
or if not possible, attempt to allocate a new block and
copies contents of current block (if successful, automatically frees old
address). Returns new address if successful or else 0.
reallocArray(buf: TypedArray, num: number)
Similar to realloc()
, but takes a typed array created with
mallocAs()
/ callocAs()
and returns new arrays of same type or
null
if re-allocation wasn't possible.
free(addr: number | TypedArray)
Releases given address or array view back into the pool. Returns true
if successful. Only previously allocated addresses or views created by
this instance can be freed and its the user's responsibility to not use
the freed address or view after this call.
freeAll()
Frees all allocated blocks, essentially resets the pool. This is useful
when using a MemPool
for temporary object allocation within a function
and then calling this method before returning. This is also much cheaper
than individually freeing the blocks.
release()
This completely destroys all internal pool references & state and should
only be used when the pool is no longer needed. The pool is unusable
afterwards. If there're no other external references to the pool's
underlying ArrayBuffer
, then it will also be garbage collected soon
after.
stats()
Returns pool statistics (see above example).
Benchmarks
node bench/index.js
1x f64x4 malloc x 8,712,284 ops/sec ±0.39% (92 runs sampled) mean: 0.00011ms
1x f64x4 vanilla x 1,714,557 ops/sec ±2.18% (82 runs sampled) mean: 0.00058ms
6x f64 malloc x 704,920 ops/sec ±1.20% (91 runs sampled) mean: 0.00142ms
6x f64 vanilla x 251,799 ops/sec ±1.87% (84 runs sampled) mean: 0.00397ms
Authors
- Karsten Schmidt
License
© 2016 - 2018 Karsten Schmidt // Apache Software License 2.0