
Security News
Rolldown Pulls Rust React Compiler Integration After Binary Size Increase
Rolldown paused Rust React Compiler integration after a 5MB binary size increase raised concerns about shipping React-specific code to all Vite users.
@nxtedition/slice
Advanced tools
A high-performance buffer slice and pool allocator for Node.js.
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 it rarely (if ever) allocates new backing stores, 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.
npm install @nxtedition/slice
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
Measured on Apple M3 Pro, Node.js v25.3.0:
| Operation | Buffer.allocUnsafe | Buffer.allocUnsafeSlow | PoolAllocator | Speedup |
|---|---|---|---|---|
| alloc 64 bytes | 38.08 ns | 41.23 ns | 5.66 ns | 6.7x |
| alloc 256 bytes | 52.09 ns | 231.46 ns | 5.90 ns | 8.8x |
| alloc 1024 bytes | 91.24 ns | 340.75 ns | 5.83 ns | 15.6x |
| alloc 4096 bytes | 446.53 ns | 437.83 ns | 6.24 ns | 71.6x |
| Operation | Buffer.allocUnsafe | Buffer.allocUnsafeSlow | PoolAllocator | Speedup |
|---|---|---|---|---|
| alloc 64 bytes | 400.46 ns | 167.94 ns | 6.33 ns | 63.3x |
| alloc 256 bytes | 309.57 ns | 500.58 ns | 6.35 ns | 48.7x |
| alloc 4096 bytes | 653.40 ns | 620.19 ns | 6.32 ns | 103.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.
Buffer.subarray| Operation | Buffer.subarray | Slice | Speedup |
|---|---|---|---|
| subarray 64 bytes | 38.11 ns | 12.99 ns | 2.9x |
| subarray 1024 bytes | 36.87 ns | 13.26 ns | 2.8x |
| subarray 64 bytes (GC) | 127.30 ns | 81.60 ns | 1.6x |
| Operation | Buffer | PoolAllocator | Speedup |
|---|---|---|---|
| alloc/free 64 bytes | 32.72 ns | 30.80 ns | 1.1x |
| alloc/free 64 bytes (GC) | 273.35 ns | 73.34 ns | 3.7x |
| alloc/free 256 bytes | 58.88 ns | 29.38 ns | 2.0x |
| realloc churn (64 → 128 → 64) | 93.63 ns | 26.99 ns | 3.5x |
| realloc in-place (grow within bucket) | 60.16 ns | 11.06 ns | 5.4x |
| 10 concurrent allocs then free | 406.26 ns | 337.83 ns | 1.2x |
| 10 concurrent allocs then free (GC) | 647.95 ns | 649.73 ns | 1.0x |
SliceA 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.
buffer: Buffer — The underlying BufferbyteOffset: number — Start offset into the bufferbyteLength: number — Current length in bytesmaxByteLength: number — Maximum capacity in byteslength: number — Alias for byteLengthreset(): 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: Buffer | Slice, targetStart?: number, sourceStart?: number, sourceEnd?: number): number — Copy data to a Buffer or Slice. Returns bytes copied.compare(target: Buffer | Slice, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): -1 | 0 | 1 — Compare with a Buffer or Slicewrite(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 sliceat(index: number): number — Read byte at index (supports negative indexing)test(expr: { test(buffer: Buffer, byteOffset: number, byteLength: number): boolean }): boolean — Test the slice against an expression objecttoString(encoding?: BufferEncoding, start?: number, end?: number): string — Convert to stringtoBuffer(start?: number, end?: number): Buffer — Return a Buffer viewSlice.EMPTY_BUF: Buffer — Shared empty buffer singletonPoolAllocatorPre-allocates a contiguous memory pool and manages slices using power-of-2 bucketing.
new PoolAllocator(poolTotal?: number)Creates a pool allocator. Default pool size is 128 MB.
realloc(slice: Slice, byteLength: number): Slice — Allocate, resize, or free a slice. Pass 0 to free.isFromPool(slice: Slice | null | undefined): boolean — Check if a slice was allocated from this poolsize: number — Total size of all active allocationsstats: { size: number, padding: number, ratio: number, poolTotal: number, poolUsed: number, poolSize: number, poolCount: number } — Detailed allocation statisticsMIT
FAQs
A high-performance buffer slice and pool allocator for Node.js.
The npm package @nxtedition/slice receives a total of 107 weekly downloads. As such, @nxtedition/slice popularity was classified as not popular.
We found that @nxtedition/slice demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 12 open source maintainers collaborating on the project.
Did you know?

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.

Security News
Rolldown paused Rust React Compiler integration after a 5MB binary size increase raised concerns about shipping React-specific code to all Vite users.

Security News
/Research
Mini Shai-Hulud expands into the Go ecosystem after hitting LeoPlatform npm packages and targeting GitHub Actions workflows.

Security News
The Fable shutdown shows how quickly model access can become a business continuity risk for AI-dependent engineering teams.