@nxtedition/slice 1.0.1 → 1.0.2
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 +2 -9
- package/lib/index.js +59 -189
- package/package.json +2 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/index.d.ts
CHANGED
|
@@ -21,15 +21,10 @@ 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
|
+
isFromPool(slice: Slice): boolean;
|
|
33
28
|
realloc(slice: Slice, byteLength: number): Slice;
|
|
34
29
|
get stats(): {
|
|
35
30
|
size: number;
|
|
@@ -39,7 +34,5 @@ export declare class BuddyAllocator {
|
|
|
39
34
|
poolUsed: number;
|
|
40
35
|
poolSize: number;
|
|
41
36
|
poolCount: number;
|
|
42
|
-
poolList: number[];
|
|
43
37
|
};
|
|
44
|
-
isFromPool(slice: Slice | null | undefined): boolean;
|
|
45
38
|
}
|
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,25 @@ export class Slice {
|
|
|
217
221
|
}
|
|
218
222
|
}
|
|
219
223
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
* Each bit tracks the XOR of a buddy pair's allocation state.
|
|
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
|
|
230
226
|
|
|
227
|
+
#pools = []
|
|
231
228
|
#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
|
|
229
|
+
#poolOffset = 0
|
|
230
|
+
#poolSize = 0
|
|
231
|
+
#poolCount = 0
|
|
232
|
+
#padding = 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)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Allocate single contiguous buffer for all bitmaps
|
|
275
|
-
this.#bitmapBuffer = new ArrayBuffer(totalWords * 4)
|
|
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))
|
|
235
|
+
this.#poolBuffer = Buffer.allocUnsafe(Number.isFinite(poolTotal) ? poolTotal : 0)
|
|
236
|
+
for (let n = 0; 2 ** n <= 256 * 1024; n++) {
|
|
237
|
+
this.#pools.push([])
|
|
284
238
|
}
|
|
239
|
+
}
|
|
285
240
|
|
|
286
|
-
|
|
287
|
-
this.#
|
|
241
|
+
isFromPool(slice ) {
|
|
242
|
+
return slice != null && slice.buffer === this.#poolBuffer
|
|
288
243
|
}
|
|
289
244
|
|
|
290
245
|
realloc(slice , byteLength ) {
|
|
@@ -292,35 +247,32 @@ export class BuddyAllocator {
|
|
|
292
247
|
throw new TypeError(`Invalid byteLength: ${byteLength}`)
|
|
293
248
|
}
|
|
294
249
|
|
|
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
250
|
if (slice.byteLength === byteLength) {
|
|
302
251
|
return slice
|
|
303
252
|
}
|
|
304
253
|
|
|
305
|
-
|
|
254
|
+
// Ceil to nearest power of two.
|
|
255
|
+
const dstIdx = byteLength <= 8 ? 3 : 32 - Math.clz32(byteLength - 1)
|
|
306
256
|
|
|
307
257
|
if (this.isFromPool(slice)) {
|
|
308
|
-
|
|
309
|
-
|
|
258
|
+
const srcIdx = 32 - Math.clz32(slice.maxByteLength - 1)
|
|
259
|
+
|
|
260
|
+
if (slice.maxByteLength !== 1 << srcIdx) {
|
|
261
|
+
throw new Error(`Invalid pool state`)
|
|
262
|
+
}
|
|
310
263
|
|
|
311
264
|
this.#size -= slice.byteLength
|
|
312
265
|
this.#padding -= slice.maxByteLength - slice.byteLength
|
|
313
266
|
|
|
314
|
-
if (
|
|
267
|
+
if (srcIdx === dstIdx) {
|
|
315
268
|
slice.byteLength = byteLength
|
|
316
269
|
this.#size += slice.byteLength
|
|
317
270
|
this.#padding += slice.maxByteLength - slice.byteLength
|
|
318
271
|
return slice
|
|
319
272
|
}
|
|
320
273
|
|
|
321
|
-
|
|
322
|
-
this.#
|
|
323
|
-
this.#poolCount--
|
|
274
|
+
this.#pools[srcIdx].push(slice.byteOffset)
|
|
275
|
+
this.#poolSize -= slice.maxByteLength
|
|
324
276
|
}
|
|
325
277
|
|
|
326
278
|
slice.reset()
|
|
@@ -329,134 +281,52 @@ export class BuddyAllocator {
|
|
|
329
281
|
return slice
|
|
330
282
|
}
|
|
331
283
|
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
284
|
+
const maxByteLength = 1 << dstIdx
|
|
285
|
+
if (this.#pools.length > 32 || maxByteLength < byteLength || (maxByteLength & 0x7) !== 0) {
|
|
286
|
+
throw new Error(`Invalid pool state`)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (dstIdx < this.#pools.length && this.#pools[dstIdx]?.length) {
|
|
290
|
+
slice.buffer = this.#poolBuffer
|
|
291
|
+
slice.byteOffset = this.#pools[dstIdx].pop()
|
|
337
292
|
slice.byteLength = byteLength
|
|
338
|
-
slice.maxByteLength =
|
|
339
|
-
|
|
293
|
+
slice.maxByteLength = maxByteLength
|
|
294
|
+
|
|
295
|
+
this.#poolSize += maxByteLength
|
|
296
|
+
} else if (
|
|
297
|
+
dstIdx < this.#pools.length &&
|
|
298
|
+
this.#poolOffset + maxByteLength <= this.#poolBuffer.byteLength
|
|
299
|
+
) {
|
|
340
300
|
slice.buffer = this.#poolBuffer
|
|
341
|
-
slice.byteOffset =
|
|
301
|
+
slice.byteOffset = this.#poolOffset
|
|
342
302
|
slice.byteLength = byteLength
|
|
343
|
-
slice.maxByteLength =
|
|
303
|
+
slice.maxByteLength = maxByteLength
|
|
344
304
|
|
|
345
|
-
this.#
|
|
346
|
-
this.#
|
|
347
|
-
this.#
|
|
305
|
+
this.#poolOffset += maxByteLength
|
|
306
|
+
this.#poolCount += 1
|
|
307
|
+
this.#poolSize += maxByteLength
|
|
308
|
+
} else {
|
|
309
|
+
slice.buffer = Buffer.allocUnsafeSlow(byteLength)
|
|
310
|
+
slice.byteOffset = 0
|
|
311
|
+
slice.byteLength = byteLength
|
|
312
|
+
slice.maxByteLength = byteLength
|
|
348
313
|
}
|
|
349
314
|
|
|
315
|
+
this.#size += slice.byteLength
|
|
316
|
+
this.#padding += slice.maxByteLength - slice.byteLength
|
|
317
|
+
|
|
350
318
|
return slice
|
|
351
319
|
}
|
|
352
320
|
|
|
353
321
|
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
322
|
return {
|
|
363
323
|
size: this.#size,
|
|
364
324
|
padding: this.#padding,
|
|
365
325
|
ratio: this.#size > 0 ? (this.#size + this.#padding) / this.#size : 1,
|
|
366
|
-
poolTotal: this.#
|
|
367
|
-
poolUsed,
|
|
368
|
-
poolSize:
|
|
326
|
+
poolTotal: this.#poolBuffer.byteLength,
|
|
327
|
+
poolUsed: this.#poolOffset,
|
|
328
|
+
poolSize: this.#poolSize,
|
|
369
329
|
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
330
|
}
|
|
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
331
|
}
|
|
462
332
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/slice",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -19,6 +19,5 @@
|
|
|
19
19
|
"amaroc": "^1.0.1",
|
|
20
20
|
"rimraf": "^6.1.2",
|
|
21
21
|
"typescript": "^5.9.3"
|
|
22
|
-
}
|
|
23
|
-
"gitHead": "2517ca631e010f17f7ba8be4e1b854ad18cbd746"
|
|
22
|
+
}
|
|
24
23
|
}
|