Files
bun.sh/test/js/web/structured-clone-fastpath.test.ts
Jarred Sumner ad1fa514ed Add fast path for simple objects in postMessage and structuredClone (#22279)
## Summary
- Extends the existing string fast path to support simple objects with
primitive values
- Achieves 2-241x performance improvements for postMessage with objects
- Maintains compatibility with existing code while significantly
reducing overhead

## Performance Results

### Bun (this PR)
```
postMessage({ prop: 11 chars string, ...9 more props }) - 648ns (was 1.36µs) 
postMessage({ prop: 14 KB string, ...9 more props })    - 719ns (was 2.09µs)
postMessage({ prop: 3 MB string, ...9 more props })      - 1.26µs (was 168µs)
```

### Node.js v24.6.0 (for comparison)
```
postMessage({ prop: 11 chars string, ...9 more props }) - 1.19µs
postMessage({ prop: 14 KB string, ...9 more props })    - 2.69µs  
postMessage({ prop: 3 MB string, ...9 more props })      - 304µs
```

## Implementation Details

The fast path activates when:
- Object is a plain object (ObjectType or FinalObjectType)
- Has no indexed properties
- All property values are primitives or strings
- No transfer list is involved

Properties are stored in a `SimpleInMemoryPropertyTableEntry` vector
that holds property names and values directly, avoiding the overhead of
full serialization.

## Test plan
- [x] Added tests for memory usage with simple objects
- [x] Added test for objects exceeding JSFinalObject::maxInlineCapacity
- [x] Created benchmark to verify performance improvements
- [x] Existing structured clone tests continue to pass

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-01 01:48:28 -07:00

108 lines
2.9 KiB
TypeScript

import { describe, expect, test } from "bun:test";
describe("Structured Clone Fast Path", () => {
test("structuredClone should work with empty object", () => {
const object = {};
const cloned = structuredClone(object);
expect(cloned).toStrictEqual({});
});
test("structuredClone should work with empty string", () => {
const string = "";
const cloned = structuredClone(string);
expect(cloned).toStrictEqual("");
});
const deOptimizations = [
{
get accessor() {
return 1;
},
},
Object.create(Object.prototype, {
data: {
value: 1,
writable: false,
configurable: false,
},
}),
Object.create(Object.prototype, {
data: {
value: 1,
writable: true,
configurable: false,
},
}),
Object.create(Object.prototype, {
data: {
get: () => 1,
configurable: true,
},
}),
Object.create(Object.prototype, {
data: {
set: () => {},
enumerable: true,
configurable: true,
},
}),
];
for (const deOptimization of deOptimizations) {
test("structuredCloneDeOptimization", () => {
structuredClone(deOptimization);
});
}
test("structuredClone should use a constant amount of memory for string inputs", () => {
const clones: Array<string> = [];
// Create a 512KB string to test fast path
const largeString = Buffer.alloc(512 * 1024, "a").toString();
for (let i = 0; i < 100; i++) {
clones.push(structuredClone(largeString));
}
Bun.gc(true);
const rss = process.memoryUsage.rss();
for (let i = 0; i < 10000; i++) {
clones.push(structuredClone(largeString));
}
Bun.gc(true);
const rss2 = process.memoryUsage.rss();
const delta = rss2 - rss;
expect(delta).toBeLessThan(1024 * 1024 * 8);
expect(clones.length).toBe(10000 + 100);
});
test("structuredClone should use a constant amount of memory for simple object inputs", () => {
// Create a 512KB string to test fast path
const largeValue = { property: Buffer.alloc(512 * 1024, "a").toString() };
for (let i = 0; i < 100; i++) {
structuredClone(largeValue);
}
Bun.gc(true);
const rss = process.memoryUsage.rss();
for (let i = 0; i < 10000; i++) {
structuredClone(largeValue);
}
Bun.gc(true);
const rss2 = process.memoryUsage.rss();
const delta = rss2 - rss;
expect(delta).toBeLessThan(1024 * 1024);
});
test("structuredClone on object with simple properties can exceed JSFinalObject::maxInlineCapacity", () => {
let largeValue = {};
for (let i = 0; i < 100; i++) {
largeValue["property" + i] = i;
}
for (let i = 0; i < 100; i++) {
expect(structuredClone(largeValue)).toStrictEqual(largeValue);
}
Bun.gc(true);
for (let i = 0; i < 100; i++) {
expect(structuredClone(largeValue)).toStrictEqual(largeValue);
}
});
});