@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.
@@ -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 (this.buffer === target && sourceStart === targetStart) {
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
- * A buddy allocator using XOR bitmap for efficient coalescing.
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
226
+ #padding = 0
230
227
 
228
+ #pools = []
231
229
  #poolBuffer
232
- #poolTotal
233
- #minOrder
234
- #maxOrder
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
- // Handle invalid pool sizes gracefully - fall back to no pool
247
- if (!Number.isFinite(poolTotal) || poolTotal <= 0) {
248
- this.#poolBuffer = Buffer.allocUnsafe(0)
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
- // 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))
284
- }
241
+ get size() {
242
+ return this.#size
243
+ }
285
244
 
286
- // Initially, the entire buffer is one free block at max order
287
- this.#freeLists[this.#maxOrder].push(0)
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
- const dstOrder = byteLength === 0 ? 0 : this.#getOrder(byteLength)
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
- if (this.isFromPool(slice)) {
308
- // Derive order from maxByteLength
309
- const srcOrder = 31 - Math.clz32(slice.maxByteLength)
264
+ if (slice.maxByteLength !== 1 << srcIdx) {
265
+ throw new Error(`Invalid pool state`)
266
+ }
310
267
 
311
- this.#size -= slice.byteLength
268
+ this.#size -= slice.maxByteLength
312
269
  this.#padding -= slice.maxByteLength - slice.byteLength
313
270
 
314
- if (srcOrder === dstOrder) {
271
+ if (srcIdx === dstIdx) {
315
272
  slice.byteLength = byteLength
316
- this.#size += slice.byteLength
273
+ this.#size += slice.maxByteLength
317
274
  this.#padding += slice.maxByteLength - slice.byteLength
318
275
  return slice
319
276
  }
320
277
 
321
- // Free the old block
322
- this.#freeBlock(slice.byteOffset, srcOrder)
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 offset = this.#allocBlock(dstOrder)
333
- if (offset == null) {
334
- // Fall back to regular allocation
335
- slice.buffer = Buffer.allocUnsafeSlow(byteLength)
336
- slice.byteOffset = 0
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 = byteLength
339
- } else {
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 = offset
305
+ slice.byteOffset = this.#poolOffset
342
306
  slice.byteLength = byteLength
343
- slice.maxByteLength = 1 << dstOrder
307
+ slice.maxByteLength = maxByteLength
344
308
 
345
- this.#poolCount++
346
- this.#size += byteLength
347
- this.#padding += slice.maxByteLength - slice.byteLength
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 ? (this.#size + this.#padding) / this.#size : 1,
366
- poolTotal: this.#poolTotal,
367
- poolUsed,
368
- poolSize: poolUsed,
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.1",
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": "2517ca631e010f17f7ba8be4e1b854ad18cbd746"
23
+ "gitHead": "a798a1529b93f64e8dd82ccc8c23579c231afdbd"
24
24
  }