@nxtedition/slice 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.bench.d.ts +1 -0
- package/lib/index.d.ts +3 -9
- package/lib/index.js +66 -192
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/index.d.ts
CHANGED
|
@@ -21,15 +21,11 @@ export declare class Slice {
|
|
|
21
21
|
[Symbol.toStringTag](): string;
|
|
22
22
|
[util.inspect.custom](depth: number, options: util.InspectOptionsStylized, inspect: typeof util.inspect): string;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
* A buddy allocator using XOR bitmap for efficient coalescing.
|
|
26
|
-
* Each bit tracks the XOR of a buddy pair's allocation state.
|
|
27
|
-
* When a bit is 0, both buddies have the same state (both free or both allocated).
|
|
28
|
-
* When a bit is 1, the buddies have different states.
|
|
29
|
-
*/
|
|
30
|
-
export declare class BuddyAllocator {
|
|
24
|
+
export declare class PoolAllocator {
|
|
31
25
|
#private;
|
|
32
26
|
constructor(poolTotal?: number);
|
|
27
|
+
get size(): number;
|
|
28
|
+
isFromPool(slice: Slice | null | undefined): boolean;
|
|
33
29
|
realloc(slice: Slice, byteLength: number): Slice;
|
|
34
30
|
get stats(): {
|
|
35
31
|
size: number;
|
|
@@ -39,7 +35,5 @@ export declare class BuddyAllocator {
|
|
|
39
35
|
poolUsed: number;
|
|
40
36
|
poolSize: number;
|
|
41
37
|
poolCount: number;
|
|
42
|
-
poolList: number[];
|
|
43
38
|
};
|
|
44
|
-
isFromPool(slice: Slice | null | undefined): boolean;
|
|
45
39
|
}
|
package/lib/index.js
CHANGED
|
@@ -81,7 +81,11 @@ export class Slice {
|
|
|
81
81
|
sourceEnd += this.byteOffset
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
if (
|
|
84
|
+
if (
|
|
85
|
+
sourceStart === targetStart &&
|
|
86
|
+
this.buffer.buffer === target.buffer &&
|
|
87
|
+
this.buffer.byteOffset === target.byteOffset
|
|
88
|
+
) {
|
|
85
89
|
return sourceEnd - sourceStart
|
|
86
90
|
}
|
|
87
91
|
|
|
@@ -217,74 +221,29 @@ export class Slice {
|
|
|
217
221
|
}
|
|
218
222
|
}
|
|
219
223
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
* When a bit is 0, both buddies have the same state (both free or both allocated).
|
|
224
|
-
* When a bit is 1, the buddies have different states.
|
|
225
|
-
*/
|
|
226
|
-
export class BuddyAllocator {
|
|
227
|
-
#size = 0
|
|
228
|
-
#padding = 0
|
|
229
|
-
#poolCount = 0
|
|
224
|
+
export class PoolAllocator {
|
|
225
|
+
#size = 0
|
|
226
|
+
#padding = 0
|
|
230
227
|
|
|
228
|
+
#pools = []
|
|
231
229
|
#poolBuffer
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
|
|
236
|
-
// Free lists indexed by order - simple arrays with push/pop
|
|
237
|
-
#freeLists = []
|
|
238
|
-
|
|
239
|
-
// XOR bitmap for coalescing - single contiguous buffer for cache locality
|
|
240
|
-
// Each order has a view into this buffer
|
|
241
|
-
// Bit = 0: both buddies same state, Bit = 1: different states
|
|
242
|
-
#bitmaps = []
|
|
243
|
-
#bitmapBuffer = null
|
|
230
|
+
#poolOffset = 0
|
|
231
|
+
#poolSize = 0
|
|
232
|
+
#poolCount = 0
|
|
244
233
|
|
|
245
234
|
constructor(poolTotal = 128 * 1024 * 1024) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
this.#
|
|
249
|
-
this.#poolTotal = 0
|
|
250
|
-
this.#minOrder = 3
|
|
251
|
-
this.#maxOrder = 0
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Round up to nearest power of 2
|
|
256
|
-
this.#maxOrder = Math.ceil(Math.log2(poolTotal))
|
|
257
|
-
this.#poolTotal = 1 << this.#maxOrder
|
|
258
|
-
|
|
259
|
-
// Minimum block size is 8 bytes (order 3)
|
|
260
|
-
this.#minOrder = 3
|
|
261
|
-
|
|
262
|
-
this.#poolBuffer = Buffer.allocUnsafe(this.#poolTotal)
|
|
263
|
-
|
|
264
|
-
// Calculate total bitmap size and offsets for each order
|
|
265
|
-
// Total pairs = 2^(maxOrder-minOrder) - 1, total words ≈ that / 32
|
|
266
|
-
let totalWords = 0
|
|
267
|
-
const offsets = []
|
|
268
|
-
for (let order = 0; order <= this.#maxOrder; order++) {
|
|
269
|
-
offsets.push(totalWords)
|
|
270
|
-
const numPairs = 1 << Math.max(0, this.#maxOrder - order - 1)
|
|
271
|
-
totalWords += Math.ceil(numPairs / 32)
|
|
235
|
+
this.#poolBuffer = Buffer.allocUnsafe(Number.isFinite(poolTotal) ? poolTotal : 0)
|
|
236
|
+
for (let n = 0; 2 ** n <= 256 * 1024; n++) {
|
|
237
|
+
this.#pools.push([])
|
|
272
238
|
}
|
|
239
|
+
}
|
|
273
240
|
|
|
274
|
-
|
|
275
|
-
this.#
|
|
276
|
-
|
|
277
|
-
// Create views into the buffer for each order
|
|
278
|
-
for (let order = 0; order <= this.#maxOrder; order++) {
|
|
279
|
-
this.#freeLists.push([])
|
|
280
|
-
|
|
281
|
-
const numPairs = 1 << Math.max(0, this.#maxOrder - order - 1)
|
|
282
|
-
const numWords = Math.ceil(numPairs / 32)
|
|
283
|
-
this.#bitmaps.push(new Uint32Array(this.#bitmapBuffer, offsets[order] * 4, numWords))
|
|
284
|
-
}
|
|
241
|
+
get size() {
|
|
242
|
+
return this.#size
|
|
243
|
+
}
|
|
285
244
|
|
|
286
|
-
|
|
287
|
-
this.#
|
|
245
|
+
isFromPool(slice ) {
|
|
246
|
+
return slice != null && slice.buffer === this.#poolBuffer
|
|
288
247
|
}
|
|
289
248
|
|
|
290
249
|
realloc(slice , byteLength ) {
|
|
@@ -292,35 +251,32 @@ export class BuddyAllocator {
|
|
|
292
251
|
throw new TypeError(`Invalid byteLength: ${byteLength}`)
|
|
293
252
|
}
|
|
294
253
|
|
|
295
|
-
if (slice == null) {
|
|
296
|
-
slice = new Slice()
|
|
297
|
-
} else if (!(slice instanceof Slice)) {
|
|
298
|
-
throw new TypeError('slice must be a Slice instance')
|
|
299
|
-
}
|
|
300
|
-
|
|
301
254
|
if (slice.byteLength === byteLength) {
|
|
302
255
|
return slice
|
|
303
256
|
}
|
|
304
257
|
|
|
305
|
-
|
|
258
|
+
// Ceil to nearest power of two.
|
|
259
|
+
const dstIdx = byteLength <= 8 ? 3 : 32 - Math.clz32(byteLength - 1)
|
|
260
|
+
|
|
261
|
+
if (slice != null && slice.buffer === this.#poolBuffer) {
|
|
262
|
+
const srcIdx = 32 - Math.clz32(slice.maxByteLength - 1)
|
|
306
263
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
264
|
+
if (slice.maxByteLength !== 1 << srcIdx) {
|
|
265
|
+
throw new Error(`Invalid pool state`)
|
|
266
|
+
}
|
|
310
267
|
|
|
311
|
-
this.#size -= slice.
|
|
268
|
+
this.#size -= slice.maxByteLength
|
|
312
269
|
this.#padding -= slice.maxByteLength - slice.byteLength
|
|
313
270
|
|
|
314
|
-
if (
|
|
271
|
+
if (srcIdx === dstIdx) {
|
|
315
272
|
slice.byteLength = byteLength
|
|
316
|
-
this.#size += slice.
|
|
273
|
+
this.#size += slice.maxByteLength
|
|
317
274
|
this.#padding += slice.maxByteLength - slice.byteLength
|
|
318
275
|
return slice
|
|
319
276
|
}
|
|
320
277
|
|
|
321
|
-
|
|
322
|
-
this.#
|
|
323
|
-
this.#poolCount--
|
|
278
|
+
this.#pools[srcIdx].push(slice.byteOffset)
|
|
279
|
+
this.#poolSize -= slice.maxByteLength
|
|
324
280
|
}
|
|
325
281
|
|
|
326
282
|
slice.reset()
|
|
@@ -329,134 +285,52 @@ export class BuddyAllocator {
|
|
|
329
285
|
return slice
|
|
330
286
|
}
|
|
331
287
|
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
288
|
+
const maxByteLength = 1 << dstIdx
|
|
289
|
+
if (this.#pools.length > 32 || maxByteLength < byteLength || (maxByteLength & 0x7) !== 0) {
|
|
290
|
+
throw new Error(`Invalid pool state`)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (dstIdx < this.#pools.length && this.#pools[dstIdx]?.length) {
|
|
294
|
+
slice.buffer = this.#poolBuffer
|
|
295
|
+
slice.byteOffset = this.#pools[dstIdx].pop()
|
|
337
296
|
slice.byteLength = byteLength
|
|
338
|
-
slice.maxByteLength =
|
|
339
|
-
|
|
297
|
+
slice.maxByteLength = maxByteLength
|
|
298
|
+
|
|
299
|
+
this.#poolSize += maxByteLength
|
|
300
|
+
} else if (
|
|
301
|
+
dstIdx < this.#pools.length &&
|
|
302
|
+
this.#poolOffset + maxByteLength <= this.#poolBuffer.byteLength
|
|
303
|
+
) {
|
|
340
304
|
slice.buffer = this.#poolBuffer
|
|
341
|
-
slice.byteOffset =
|
|
305
|
+
slice.byteOffset = this.#poolOffset
|
|
342
306
|
slice.byteLength = byteLength
|
|
343
|
-
slice.maxByteLength =
|
|
307
|
+
slice.maxByteLength = maxByteLength
|
|
344
308
|
|
|
345
|
-
this.#
|
|
346
|
-
this.#
|
|
347
|
-
this.#
|
|
309
|
+
this.#poolOffset += maxByteLength
|
|
310
|
+
this.#poolCount += 1
|
|
311
|
+
this.#poolSize += maxByteLength
|
|
312
|
+
} else {
|
|
313
|
+
slice.buffer = Buffer.allocUnsafeSlow(byteLength)
|
|
314
|
+
slice.byteOffset = 0
|
|
315
|
+
slice.byteLength = byteLength
|
|
316
|
+
slice.maxByteLength = byteLength
|
|
348
317
|
}
|
|
349
318
|
|
|
319
|
+
this.#size += slice.maxByteLength
|
|
320
|
+
this.#padding += slice.maxByteLength - slice.byteLength
|
|
321
|
+
|
|
350
322
|
return slice
|
|
351
323
|
}
|
|
352
324
|
|
|
353
325
|
get stats() {
|
|
354
|
-
// Calculate free space from all free lists
|
|
355
|
-
let freeSpace = 0
|
|
356
|
-
for (let order = this.#minOrder; order <= this.#maxOrder; order++) {
|
|
357
|
-
freeSpace += this.#freeLists[order].length << order
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const poolUsed = this.#poolTotal - freeSpace
|
|
361
|
-
|
|
362
326
|
return {
|
|
363
327
|
size: this.#size,
|
|
364
328
|
padding: this.#padding,
|
|
365
|
-
ratio: this.#size > 0 ?
|
|
366
|
-
poolTotal: this.#
|
|
367
|
-
poolUsed,
|
|
368
|
-
poolSize:
|
|
329
|
+
ratio: this.#size > 0 ? this.#size / (this.#size - this.#padding) : 1,
|
|
330
|
+
poolTotal: this.#poolBuffer.byteLength,
|
|
331
|
+
poolUsed: this.#poolOffset,
|
|
332
|
+
poolSize: this.#poolSize,
|
|
369
333
|
poolCount: this.#poolCount,
|
|
370
|
-
poolList: this.#freeLists.map((list) => list.length),
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
isFromPool(slice ) {
|
|
375
|
-
return slice != null && slice.buffer === this.#poolBuffer
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
#toggleBit(offset , order ) {
|
|
379
|
-
if (order >= this.#maxOrder) {
|
|
380
|
-
return false
|
|
381
|
-
}
|
|
382
|
-
const pairIndex = offset >>> (order + 1)
|
|
383
|
-
const wordIndex = pairIndex >>> 5
|
|
384
|
-
const bitMask = 1 << (pairIndex & 31)
|
|
385
|
-
const bitmap = this.#bitmaps[order]
|
|
386
|
-
bitmap[wordIndex] ^= bitMask
|
|
387
|
-
return (bitmap[wordIndex] & bitMask) !== 0
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
#allocBlock(order ) {
|
|
391
|
-
// Try to allocate from pool
|
|
392
|
-
if (order > this.#maxOrder || this.#poolTotal === 0) {
|
|
393
|
-
return null
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Find the smallest order with a free block
|
|
397
|
-
let currentOrder = order
|
|
398
|
-
while (currentOrder <= this.#maxOrder && this.#freeLists[currentOrder].length === 0) {
|
|
399
|
-
currentOrder++
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (currentOrder > this.#maxOrder) {
|
|
403
|
-
return null
|
|
404
334
|
}
|
|
405
|
-
|
|
406
|
-
// Pop the free block
|
|
407
|
-
const offset = this.#freeLists[currentOrder].pop()
|
|
408
|
-
|
|
409
|
-
// Found block at exact order - just toggle and return
|
|
410
|
-
if (currentOrder === order) {
|
|
411
|
-
this.#toggleBit(offset, order)
|
|
412
|
-
return offset
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Split down to the requested order
|
|
416
|
-
while (currentOrder > order) {
|
|
417
|
-
currentOrder--
|
|
418
|
-
// Add buddy to free list
|
|
419
|
-
const buddyOffset = offset ^ (1 << currentOrder)
|
|
420
|
-
this.#freeLists[currentOrder].push(buddyOffset)
|
|
421
|
-
// Toggle bit - one buddy free, one allocated/kept
|
|
422
|
-
this.#toggleBit(offset, currentOrder)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return offset
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
#freeBlock(offset , order ) {
|
|
429
|
-
while (order < this.#maxOrder) {
|
|
430
|
-
// Toggle bit and check if we can coalesce
|
|
431
|
-
const isDifferent = this.#toggleBit(offset, order)
|
|
432
|
-
|
|
433
|
-
if (isDifferent) {
|
|
434
|
-
// Bit is now 1: buddy is allocated, can't coalesce
|
|
435
|
-
this.#freeLists[order].push(offset)
|
|
436
|
-
return
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Bit is now 0: both buddies are free, coalesce
|
|
440
|
-
// Remove buddy from free list
|
|
441
|
-
const buddyOffset = offset ^ (1 << order)
|
|
442
|
-
const idx = this.#freeLists[order].indexOf(buddyOffset)
|
|
443
|
-
if (idx !== -1) {
|
|
444
|
-
// Swap with last and pop for O(1) removal
|
|
445
|
-
const last = this.#freeLists[order].length - 1
|
|
446
|
-
this.#freeLists[order][idx] = this.#freeLists[order][last]
|
|
447
|
-
this.#freeLists[order].pop()
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Move up to parent level
|
|
451
|
-
offset = offset & ~(1 << order)
|
|
452
|
-
order++
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Reached max order
|
|
456
|
-
this.#freeLists[order].push(offset)
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
#getOrder(byteLength ) {
|
|
460
|
-
return byteLength <= 1 << this.#minOrder ? this.#minOrder : 32 - Math.clz32(byteLength - 1)
|
|
461
335
|
}
|
|
462
336
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/slice",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -20,5 +20,5 @@
|
|
|
20
20
|
"rimraf": "^6.1.2",
|
|
21
21
|
"typescript": "^5.9.3"
|
|
22
22
|
},
|
|
23
|
-
"gitHead": "
|
|
23
|
+
"gitHead": "a798a1529b93f64e8dd82ccc8c23579c231afdbd"
|
|
24
24
|
}
|