@nxtedition/slice
Advanced tools
+21
| MIT License | ||
| Copyright (c) nxtedition | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+126
| # @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 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. | ||
| ## Install | ||
| ```sh | ||
| npm install @nxtedition/slice | ||
| ``` | ||
| ## Usage | ||
| ```js | ||
| 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 | ||
| | Operation | `Buffer.allocUnsafe` | `Buffer.allocUnsafeSlow` | `PoolAllocator` | Speedup | | ||
| | ---------------- | -------------------- | ------------------------ | --------------- | ------- | | ||
| | alloc 64 bytes | 28.53 ns | 31.33 ns | **3.87 ns** | 7.4x | | ||
| | alloc 256 bytes | 36.98 ns | 170.56 ns | **4.34 ns** | 8.5x | | ||
| | alloc 1024 bytes | 65.48 ns | 238.49 ns | **4.36 ns** | 15.0x | | ||
| | alloc 4096 bytes | 301.19 ns | 292.50 ns | **4.29 ns** | 70.2x | | ||
| ### Slice creation vs `Buffer.subarray` | ||
| | Operation | `Buffer.subarray` | `Slice` | Speedup | | ||
| | ------------------- | ----------------- | ----------- | ------- | | ||
| | subarray 64 bytes | 27.32 ns | **9.68 ns** | 2.8x | | ||
| | subarray 1024 bytes | 26.75 ns | **9.52 ns** | 2.8x | | ||
| ### Combined operations | ||
| | Operation | `Buffer.subarray` | `Slice` | Speedup | | ||
| | ------------------------------------- | ----------------- | ------------- | ------- | | ||
| | subarray + toString (64 bytes) | 78.89 ns | **60.12 ns** | 1.3x | | ||
| | alloc/free 64 bytes | 25.24 ns | **22.82 ns** | 1.1x | | ||
| | alloc/free 256 bytes | 34.07 ns | **22.63 ns** | 1.5x | | ||
| | realloc churn (64 → 128 → 64) | 70.20 ns | **20.80 ns** | 3.4x | | ||
| | realloc in-place (grow within bucket) | 45.41 ns | **8.28 ns** | 5.5x | | ||
| | 10 concurrent allocs then free | 312.96 ns | **259.34 ns** | 1.2x | | ||
| ## 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: 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 `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 | ||
| - `at(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 object | ||
| - `toString(encoding?: BufferEncoding, start?: number, end?: number): string` — Convert to string | ||
| - `toBuffer(start?: number, end?: number): Buffer` — Return a `Buffer` view | ||
| #### 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(poolTotal?: number)` | ||
| Creates a pool allocator. Default pool size is 128 MB. | ||
| #### Methods | ||
| - `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 pool | ||
| #### Properties | ||
| - `size: number` — Total size of all active allocations | ||
| - `stats: { size: number, padding: number, ratio: number, poolTotal: number, poolUsed: number, poolSize: number, poolCount: number }` — Detailed allocation statistics | ||
| ## License | ||
| MIT |
+13
-6
| { | ||
| "name": "@nxtedition/slice", | ||
| "version": "1.0.4", | ||
| "version": "1.0.7", | ||
| "type": "module", | ||
@@ -8,5 +8,10 @@ "main": "lib/index.js", | ||
| "files": [ | ||
| "lib" | ||
| "lib", | ||
| "README.md", | ||
| "LICENSE" | ||
| ], | ||
| "license": "UNLICENSED", | ||
| "license": "MIT", | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "scripts": { | ||
@@ -16,4 +21,5 @@ "build": "rimraf lib && tsc && amaroc ./src/index.ts && mv src/index.js lib/", | ||
| "typecheck": "tsc --noEmit", | ||
| "test": "node --test", | ||
| "test:ci": "node --test" | ||
| "test": "yarn build && node --test", | ||
| "test:ci": "yarn build && node --test", | ||
| "test:coverage": "node --test --experimental-test-coverage" | ||
| }, | ||
@@ -26,3 +32,4 @@ "devDependencies": { | ||
| "typescript": "^5.9.3" | ||
| } | ||
| }, | ||
| "gitHead": "8dbd8386c6d4c511ffa81a904c58b6f648811a52" | ||
| } |
Explicitly Unlicensed Item
LicenseSomething was found which is explicitly marked as unlicensed.
Misc. License Issues
LicenseA package's licensing information has fine-grained problems.
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
18878
78.57%6
50%0
-100%0
-100%100
Infinity%0
-100%127
Infinity%