🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@nxtedition/slice

Package Overview
Dependencies
Maintainers
12
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nxtedition/slice

A high-performance buffer slice and pool allocator for Node.js.

npmnpm
Version
1.1.10
Version published
Weekly downloads
116
-29.27%
Maintainers
12
Weekly downloads
 
Created
Source

@nxtedition/slice

A high-performance buffer slice and pool allocator for Node.js.

Why

Node.js Buffer.subarray() is slow. Every call creates a new Buffer object — a typed array wrapper with prototype chain setup, internal slot initialization, and bounds validation. This overhead is negligible for occasional use, but becomes a bottleneck in hot paths — protocol parsers, binary codecs, streaming pipelines — where thousands of sub-views are created per second.

Buffer.allocUnsafe() is worse. Allocations above the pool size (Buffer.poolSize) threshold go through allocBuffer which crosses into C++ to create a new ArrayBuffer backing store. The pooled fast path still involves bookkeeping and pool management overhead, and every allocation produces a new Buffer object that the GC must eventually collect.

Slice avoids this entirely. It is a plain JavaScript object with buffer, byteOffset, and byteLength fields. Creating a slice is just setting three properties — no typed array wrapper creation, no GC pressure from short-lived Buffer objects. Operations like toString, copy, and compare delegate directly to the underlying buffer with the correct offsets.

PoolAllocator takes this further. Like Node's internal pool, it has management overhead — but for in-pool sizes it never allocates new backing stores (only allocations larger than the 256 KB top bucket, or made once the contiguous pool is exhausted, fall back to a standalone Buffer), and because Slice is a plain object rather than a typed array, resizing or freeing a slice doesn't produce garbage for V8 to collect. It pre-allocates a large contiguous buffer and hands out regions using power-of-2 bucketing. When a slice is freed, its slot is recycled. When a slice is resized within the same bucket, no data moves at all — just a field update. This gives you malloc/realloc/free semantics with near-zero overhead per operation. The trade-off is upfront memory allocation and internal fragmentation from power-of-2 rounding — a 10-byte allocation uses a 16-byte slot. Buckets are also independent: a freed 16-byte slot cannot satisfy a 32-byte request, so the pool can become fragmented if allocation sizes are uneven. Use stats to monitor pool utilization and tune the pool size for your workload.

Install

npm install @nxtedition/slice

Usage

import { Slice, PoolAllocator } from '@nxtedition/slice'

// Create a slice from an existing buffer
const buf = Buffer.from('hello world')
const slice = new Slice(buf, 6, 5)
slice.toString() // 'world'

// Use a pool allocator for high-throughput allocation
const pool = new PoolAllocator()
const s = new Slice()

pool.realloc(s, 64) // allocate 64 bytes from pool
s.write('hello')
pool.realloc(s, 128) // grow — may reuse same slot
pool.realloc(s, 0) // free — slot is recycled

Benchmarks

Measured on Apple M3 Pro, Node.js v25.3.0:

Allocation

OperationBuffer.allocUnsafeBuffer.allocUnsafeSlowPoolAllocatorSpeedup
alloc 64 bytes38.08 ns41.23 ns5.66 ns6.7x
alloc 256 bytes52.09 ns231.46 ns5.90 ns8.8x
alloc 1024 bytes91.24 ns340.75 ns5.83 ns15.6x
alloc 4096 bytes446.53 ns437.83 ns6.24 ns71.6x

Allocation (GC)

OperationBuffer.allocUnsafeBuffer.allocUnsafeSlowPoolAllocatorSpeedup
alloc 64 bytes400.46 ns167.94 ns6.33 ns63.3x
alloc 256 bytes309.57 ns500.58 ns6.35 ns48.7x
alloc 4096 bytes653.40 ns620.19 ns6.32 ns103.4x

Under GC pressure, the advantage grows dramatically — up to 103x faster — because PoolAllocator reuses slots from a pre-allocated buffer and never creates objects for V8 to trace.

Slice creation vs Buffer.subarray

OperationBuffer.subarraySliceSpeedup
subarray 64 bytes38.11 ns12.99 ns2.9x
subarray 1024 bytes36.87 ns13.26 ns2.8x
subarray 64 bytes (GC)127.30 ns81.60 ns1.6x

Combined operations

OperationBufferPoolAllocatorSpeedup
alloc/free 64 bytes32.72 ns30.80 ns1.1x
alloc/free 64 bytes (GC)273.35 ns73.34 ns3.7x
alloc/free 256 bytes58.88 ns29.38 ns2.0x
realloc churn (64 → 128 → 64)93.63 ns26.99 ns3.5x
realloc in-place (grow within bucket)60.16 ns11.06 ns5.4x
10 concurrent allocs then free406.26 ns337.83 ns1.2x
10 concurrent allocs then free (GC)647.95 ns649.73 ns1.0x

API

Slice

A lightweight view over a Buffer with explicit offset and length tracking.

new Slice(buffer?: Buffer, byteOffset?: number, byteLength?: number, maxByteLength?: number)

Creates a new slice. All parameters are optional — defaults to an empty slice.

Properties

  • buffer: Buffer — The underlying Buffer
  • byteOffset: number — Start offset into the buffer
  • byteLength: number — Current length in bytes
  • maxByteLength: number — Maximum capacity in bytes
  • length: number — Alias for byteLength

Methods

  • reset(): void — Clear the slice back to empty state. Note: this does not return the slot to the PoolAllocator — you must call realloc(slice, 0) to free pool memory.
  • copy(target: Uint8Array | Slice, targetStart?: number, sourceStart?: number, sourceEnd?: number): number — Copy data to a Uint8Array/Buffer or Slice. Returns bytes copied.
  • compare(target: Uint8Array | Slice, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): -1 | 0 | 1 — Compare with a Uint8Array/Buffer or Slice
  • write(string: string, offset?: number, length?: number, encoding?: BufferEncoding): number — Write a string into the slice. Returns bytes written.
  • set(source: Buffer | Slice | null | undefined, offset?: number): void — Copy from a Buffer or Slice into this slice. (A plain Uint8Array source is not accepted — it has no copy method.)
  • at(index: number): number — Read byte at integer index (supports negative indexing)
  • test(expr: { test(buffer: Buffer, byteOffset: number, byteLength: number): boolean }): boolean — Test the slice against an expression object
  • toString(encoding?: BufferEncoding, start?: number, end?: number): string — Convert to string
  • toBuffer(start?: number, end?: number): Buffer — Return a Buffer view

Validation & bounds

All offsets are relative to the slice (i.e. 0 is byteOffset). For a Slice target, target offsets are relative to that slice; for a raw Buffer/Uint8Array target they are absolute (passed straight through to the underlying Buffer method).

The rule is consistent across the API:

  • Start/offset arguments are validatedset's offset, copy/compare's sourceStart/targetStart, toString/toBuffer's start, write's offset/length, and at's index must be in-range integers. Out-of-range or non-integer values throw RangeError. This prevents a negative offset from resolving to a position before the slice and reading/writing adjacent (pool) memory.
  • End arguments are clampedsourceEnd/targetEnd/end are clamped to the slice's logical length (matching Buffer's lenient end-of-range behavior), so over-long ranges never read past the slice's end.

Static

  • Slice.EMPTY_BUF: Buffer — Shared empty buffer singleton

PoolAllocator

Pre-allocates a contiguous memory pool and manages slices using power-of-2 bucketing.

new PoolAllocator(poolTotalOrBuffer?: number | Buffer | ArrayBufferView | ArrayBuffer | SharedArrayBuffer)

Creates a pool allocator. Pass a byte size to allocate a fresh backing buffer (default 128 MB, must be a non-negative integer), or pass an existing Buffer/ArrayBufferView/ArrayBuffer/SharedArrayBuffer to back the pool with caller-provided memory.

Single-owner. The allocator's bookkeeping lives in the instance, not in the backing buffer. When you supply your own buffer, that buffer must be owned exclusively by this allocator: do not build your own Slice views over it, do not share it with a second PoolAllocator, and (for a SharedArrayBuffer) do not allocate from more than one thread — the metadata is not shared or atomic, so doing any of these silently produces overlapping allocations.

Methods

  • realloc(byteLength: number): Slice — Allocate a fresh slice.
  • realloc(slice: Slice, byteLength: number): Slice — Resize a slice, or free it by passing 0. Contents are not preservedrealloc has malloc semantics, not C realloc semantics; after a resize the bytes are undefined (a same-bucket resize happens to keep them in place, but do not rely on it). Only call realloc with a slice that belongs to this allocator (or a fresh/empty Slice); passing a slice from another pool, or freeing the same slice twice, corrupts the allocator's accounting.
  • isFromPool(slice: Slice | null | undefined): boolean — Check if a slice's buffer is this pool's backing buffer. Note this is an identity check; it returns true for any slice over the same buffer, not only ones this allocator handed out.

Allocations larger than 256 KB (the largest bucket), or made when the contiguous pool is exhausted, fall back to a fresh standalone Buffer (isFromPool returns false) and are excluded from size/stats.

Properties

  • size: number — Total reserved bytes of all active pool allocations (sum of bucket sizes; equals stats.poolSize).
  • stats — Detailed allocation statistics:
    • size — same as the size getter (active pool bytes, including power-of-2 padding).
    • padding — bytes lost to power-of-2 rounding across active pool slices.
    • ratiosize / (size - padding); 1 when there is no padding.
    • poolTotal — capacity of the backing buffer in bytes.
    • poolUsed — bump-pointer high-water mark; monotonic, never decreases.
    • poolSize — active pool bytes (same as size).
    • poolCount — number of distinct slots ever bump-allocated (monotonic high-water count, not a live count).
    • buckets — per power-of-2 bucket: { free, used, size }.

License

MIT

FAQs

Package last updated on 01 Jun 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts