Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-23 - [TypedArray View Overhead]
**Learning:** In V8 (Node 20+), creating `Uint8Array.subarray` views inside a hot loop (100k+ iterations) for small blocks (<50 bytes) incurs significant overhead, making manual byte copying loops faster.
**Action:** Use hybrid approach: manual copy for small blocks, `subarray` + `set` for large blocks.
19 changes: 12 additions & 7 deletions src/array/ShareableArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,8 @@ export class ShareableArray<T> extends TransferableDataStructure {
*/
private defragment() {
const newData: ArrayBuffer = new ArrayBuffer(this.dataView.byteLength);
const newView = new DataView(newData);
const newDataU8 = new Uint8Array(newData);
const oldDataU8 = new Uint8Array(this.dataMem);

let currentDataStart = 0;

Expand All @@ -955,21 +956,25 @@ export class ShareableArray<T> extends TransferableDataStructure {
const currentDataPos = this.indexView.getUint32(ShareableArray.INDEX_TABLE_OFFSET + 4 * i);
const currentObjectLength = this.dataView.getUint32(currentDataPos + 4) + ShareableArray.DATA_OBJECT_OFFSET;

// Copy all bytes to the new array
for (let i = 0; i < currentObjectLength; i++) {
newView.setUint8(currentDataStart + i, this.dataView.getUint8(currentDataPos + i));
// Use manual copy for small blocks to avoid TypedArray view overhead.
// For larger blocks, use the optimized native set method.
if (currentObjectLength < 64) {
for (let i = 0; i < currentObjectLength; i++) {
newDataU8[currentDataStart + i] = oldDataU8[currentDataPos + i];
}
} else {
newDataU8.set(oldDataU8.subarray(currentDataPos, currentDataPos + currentObjectLength), currentDataStart);
}

// Update the position where this is stored in the index array
this.indexView.setUint32(ShareableArray.INDEX_TABLE_OFFSET + 4 * i, currentDataStart);

// Update the starting position in the new defragmented array
currentDataStart += currentObjectLength + ShareableArray.DATA_OBJECT_OFFSET;
currentDataStart += currentObjectLength;
}

// Replace the data from the old data array with the data in the array
const oldArray = new Uint8Array(this.dataMem);
oldArray.set(new Uint8Array(newData));
oldDataU8.set(newDataU8);

// Update where the free space in the data array starts again
this.freeStart = currentDataStart;
Expand Down
15 changes: 11 additions & 4 deletions src/map/ShareableMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,8 @@ export class ShareableMap<K, V> extends TransferableDataStructure {
private defragment() {
const newData: ArrayBuffer = new ArrayBuffer(this.dataView.byteLength);
const newView = new DataView(newData);
const newDataU8 = new Uint8Array(newData);
const oldDataU8 = new Uint8Array(this.dataMem);

let newOffset = ShareableMap.INITIAL_DATA_OFFSET;

Expand All @@ -550,8 +552,14 @@ export class ShareableMap<K, V> extends TransferableDataStructure {

const totalLength = keyLength + valueLength + ShareableMap.DATA_OBJECT_OFFSET;

for (let i = 0; i < totalLength; i++) {
newView.setUint8(newOffset + i, this.dataView.getUint8(dataPointer + i));
// Use manual copy for small blocks to avoid TypedArray view overhead.
// For larger blocks, use the optimized native set method.
if (totalLength < 64) {
for (let i = 0; i < totalLength; i++) {
newDataU8[newOffset + i] = oldDataU8[dataPointer + i];
}
} else {
newDataU8.set(oldDataU8.subarray(dataPointer, dataPointer + totalLength), newOffset);
}

// Pointer to next block is zero
Expand All @@ -571,8 +579,7 @@ export class ShareableMap<K, V> extends TransferableDataStructure {
}

// Replace the data from the old data array with the data in the array
const oldArray = new Uint8Array(this.dataMem);
oldArray.set(new Uint8Array(newData));
oldDataU8.set(newDataU8);

this.freeStart = newOffset;
}
Expand Down