mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
## 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>
152 lines
6.5 KiB
JavaScript
152 lines
6.5 KiB
JavaScript
// This snippet mostly exists to reproduce a memory leak
|
|
import { bench, run } from "../runner.mjs";
|
|
|
|
const obj = {
|
|
"id": 1296269,
|
|
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
|
|
"name": "Hello-World",
|
|
"full_name": "octocat/Hello-World",
|
|
"owner": {
|
|
"login": "octocat",
|
|
"id": 1,
|
|
"node_id": "MDQ6VXNlcjE=",
|
|
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
|
"gravatar_id": "",
|
|
"url": "https://api.github.com/users/octocat",
|
|
"html_url": "https://github.com/octocat",
|
|
"followers_url": "https://api.github.com/users/octocat/followers",
|
|
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
|
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
|
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
|
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
|
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
|
"repos_url": "https://api.github.com/users/octocat/repos",
|
|
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
|
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
|
"type": "User",
|
|
"site_admin": false,
|
|
},
|
|
"private": false,
|
|
"html_url": "https://github.com/octocat/Hello-World",
|
|
"description": "This your first repo!",
|
|
"fork": false,
|
|
"url": "https://api.github.com/repos/octocat/Hello-World",
|
|
"archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}",
|
|
"assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}",
|
|
"blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}",
|
|
"branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}",
|
|
"collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}",
|
|
"comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}",
|
|
"commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}",
|
|
"compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}",
|
|
"contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}",
|
|
"contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors",
|
|
"deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments",
|
|
"downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads",
|
|
"events_url": "https://api.github.com/repos/octocat/Hello-World/events",
|
|
"forks_url": "https://api.github.com/repos/octocat/Hello-World/forks",
|
|
"git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}",
|
|
"git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}",
|
|
"git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}",
|
|
"git_url": "git:github.com/octocat/Hello-World.git",
|
|
"issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}",
|
|
"issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}",
|
|
"issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}",
|
|
"keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}",
|
|
"labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}",
|
|
"languages_url": "https://api.github.com/repos/octocat/Hello-World/languages",
|
|
"merges_url": "https://api.github.com/repos/octocat/Hello-World/merges",
|
|
"milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}",
|
|
"notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}",
|
|
"pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}",
|
|
"releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}",
|
|
"ssh_url": "git@github.com:octocat/Hello-World.git",
|
|
"stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers",
|
|
"statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}",
|
|
"subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers",
|
|
"subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription",
|
|
"tags_url": "https://api.github.com/repos/octocat/Hello-World/tags",
|
|
"teams_url": "https://api.github.com/repos/octocat/Hello-World/teams",
|
|
"trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}",
|
|
"clone_url": "https://github.com/octocat/Hello-World.git",
|
|
"mirror_url": "git:git.example.com/octocat/Hello-World",
|
|
"hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks",
|
|
"svn_url": "https://svn.github.com/octocat/Hello-World",
|
|
"homepage": "https://github.com",
|
|
"language": null,
|
|
"forks_count": 9,
|
|
"stargazers_count": 80,
|
|
"watchers_count": 80,
|
|
"size": 108,
|
|
"default_branch": "master",
|
|
"open_issues_count": 0,
|
|
"is_template": false,
|
|
"topics": ["octocat", "atom", "electron", "api"],
|
|
"has_issues": true,
|
|
"has_projects": true,
|
|
"has_wiki": true,
|
|
"has_pages": false,
|
|
"has_downloads": true,
|
|
"has_discussions": false,
|
|
"archived": false,
|
|
"disabled": false,
|
|
"visibility": "public",
|
|
"pushed_at": "2011-01-26T19:06:43Z",
|
|
"created_at": "2011-01-26T19:01:12Z",
|
|
"updated_at": "2011-01-26T19:14:43Z",
|
|
"permissions": {
|
|
"admin": false,
|
|
"push": false,
|
|
"pull": true,
|
|
},
|
|
"security_and_analysis": {
|
|
"advanced_security": {
|
|
"status": "enabled",
|
|
},
|
|
"secret_scanning": {
|
|
"status": "enabled",
|
|
},
|
|
"secret_scanning_push_protection": {
|
|
"status": "disabled",
|
|
},
|
|
},
|
|
};
|
|
|
|
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("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();
|