perf(Response.json): use JSC's FastStringifier by passing undefined for space (#25717)

## Summary

- Fix performance regression where `Response.json()` was 2-3x slower
than `JSON.stringify() + new Response()`
- Root cause: The existing code called `JSC::JSONStringify` with
`indent=0`, which internally passes `jsNumber(0)` as the space
parameter. This bypasses WebKit's FastStringifier optimization.
- Fix: Add a new `jsonStringifyFast` binding that passes `jsUndefined()`
for the space parameter, triggering JSC's FastStringifier
(SIMD-optimized) code path.

## Root Cause Analysis

In WebKit's `JSONObject.cpp`, the `stringify()` function has this logic:

```cpp
static NEVER_INLINE String stringify(JSGlobalObject& globalObject, JSValue value, JSValue replacer, JSValue space)
{
    // ...
    if (String result = FastStringifier<Latin1Character, BufferMode::StaticBuffer>::stringify(globalObject, value, replacer, space, failureReason); !result.isNull())
        return result;
    // Falls back to slow Stringifier...
}
```

And `FastStringifier::stringify()` checks:
```cpp
if (!space.isUndefined()) {
    logOutcome("space"_s);
    return { };  // Bail out to slow path
}
```

So when we called `JSONStringify(globalObject, value, (unsigned)0)`, it
converted to `jsNumber(0)` which is NOT `undefined`, causing
FastStringifier to bail out.

## Performance Results

### Before (3.5x slower than manual approach)
```
Response.json():                2415ms
JSON.stringify() + Response():  689ms
Ratio:                          3.50x
```

### After (parity with manual approach)
```
Response.json():                ~700ms  
JSON.stringify() + Response():  ~700ms
Ratio:                          ~1.09x
```

## Test plan

- [x] Existing `Response.json()` tests pass
(`test/regression/issue/21257.test.ts`)
- [x] Response tests pass (`test/js/web/fetch/response.test.ts`)
- [x] Manual verification that output is correct for various JSON inputs

Fixes #25693

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

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Sosuke Suzuki <sosuke@bun.com>
This commit is contained in:
robobun
2025-12-27 15:01:28 -08:00
committed by GitHub
parent 01de0ecbd9
commit 1d7cb4bbad
5 changed files with 62 additions and 8 deletions

View File

@@ -112,12 +112,40 @@ const obj = {
},
};
bench("Response.json(obj)", async () => {
const smallObj = { id: 1, name: "test" };
const arrayObj = {
items: Array.from({ length: 100 }, (_, i) => ({ id: i, value: `item-${i}` })),
};
bench("Response.json(obj)", () => {
return Response.json(obj);
});
bench("Response.json(obj).json()", async () => {
return await Response.json(obj).json();
bench("new Response(JSON.stringify(obj))", () => {
return new Response(JSON.stringify(obj), {
headers: { "Content-Type": "application/json" },
});
});
bench("Response.json(smallObj)", () => {
return Response.json(smallObj);
});
bench("new Response(JSON.stringify(smallObj))", () => {
return new Response(JSON.stringify(smallObj), {
headers: { "Content-Type": "application/json" },
});
});
bench("Response.json(arrayObj)", () => {
return Response.json(arrayObj);
});
bench("new Response(JSON.stringify(arrayObj))", () => {
return new Response(JSON.stringify(arrayObj), {
headers: { "Content-Type": "application/json" },
});
});
await run();