Title: Backport v8 "Reduce stack frame summarization costs" patch for React development speedup · Issue #64879 · nodejs/node · GitHub
Open Graph Title: Backport v8 "Reduce stack frame summarization costs" patch for React development speedup · Issue #64879 · nodejs/node
X Title: Backport v8 "Reduce stack frame summarization costs" patch for React development speedup · Issue #64879 · nodejs/node
Description: Summary Please backport V8 commit v8/v8@c9c0abf, “Reduce stack frame summarization costs,” to the Node.js 24 LTS release line. The backport should also include the required follow-up fix v8/v8@1a00890, which falls back to the full frame ...
Open Graph Description: Summary Please backport V8 commit v8/v8@c9c0abf, “Reduce stack frame summarization costs,” to the Node.js 24 LTS release line. The backport should also include the required follow-up fix v8/v8@1a00...
X Description: Summary Please backport V8 commit v8/v8@c9c0abf, “Reduce stack frame summarization costs,” to the Node.js 24 LTS release line. The backport should also include the required follow-up fix v8/v8@1a00...
Opengraph URL: https://github.com/nodejs/node/issues/64879
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Backport v8 \"Reduce stack frame summarization costs\" patch for React development speedup","articleBody":"## Summary\n\nPlease backport V8 commit https://github.com/v8/v8/commit/c9c0abfa51f06ea3c87c4ee9bb519fa8b01fcbbf, “Reduce stack frame summarization costs,” to the Node.js 24 LTS release line.\n\nThe backport should also include the required follow-up fix https://github.com/v8/v8/commit/1a0089053443ae4001fd15b84f1b46f330a0b3fa, which falls back to the full frame walk when a receiver is unboxed.\n\nThe optimization substantially reduces the synchronous cost of constructing `Error` objects with stack capture enabled. This cost is paid even before `.stack` is read because V8 eagerly collects the raw stack-frame information at construction time; only conversion to the formatted stack string is lazy.\n\n## React use case\n\nThere are two paths in React that heavily rely on `Error()` in dev.\n\n### React element creation\n\nReact constructs `Error` objects in the development implementations of `jsx`, `jsxDEV`, and `createElement`.\n\nEvery JSX expression is compiled into one of these element-creation calls. For example:\n\n```jsx\nfunction App() {\n return (\n \u003cLayout>\n \u003cHeader />\n \u003cContent />\n \u003c/Layout>\n );\n}\n```\n\nEach element in this tree can cause React to capture an `Error`:\n\n```js\nconst debugStack = Error('react-stack-top-frame');\n```\n\nThe error is not thrown. React stores it as the element’s development-only `_debugStack` field together with its `_owner`:\n\n```js\nelement._debugStack = debugStack;\nelement._owner = currentOwner;\n```\n\nWhen the element becomes a Fiber, these fields are copied to `fiber._debugStack` and `fiber._debugOwner`. React can later format the captured error to determine the source location where the owner created that element.\n\nThis is used for:\n\n- Owner stacks attached to development warnings and errors.\n- `React.captureOwnerStack()`.\n- Component source information displayed by React DevTools.\n- Key and invalid-child warnings.\n- Server-rendering and RSC debug information.\n\nReact uses sentinel frames such as `react-stack-top-frame` and `react_stack_bottom_frame` to remove the JSX runtime and other React internals when the error is eventually formatted. The resulting owner stack points back to the application component that created the element.\n\nThe capture occurs in several development element-creation paths:\n\n- `jsxDEV`, used by the development JSX transform.\n- The development implementations of `jsx` and `jsxs`, including dependencies compiled with the production JSX signature but loaded in a development React build.\n- `createElement`, used by the classic JSX transform, manual `React.createElement` calls, and some libraries.\n\nBecause JSX element creation is fundamental to rendering, this is a very hot path. A component tree may create hundreds or thousands of elements, and the captures happen again when components re-render.\n\nReact rate-limits detailed owner-stack collection to 10,000 recently created elements. After that it reuses a shared “unknown owner” stack until the counter is periodically reset. Consequently, a development workload can still perform up to 10,000 real `Error()` captures during each reset period.\n\nThis path already temporarily caps `Error.stackTraceLimit` at 10 to avoid inheriting a much higher application-configured value. However, the V8 optimization remains valuable: in the Node.js 24 benchmark, the patch made `Error()` construction 2.17× faster even at a stack limit of 10.\n\nThese captures are development-only, but development performance is important for server rendering, tests, local application startup, and update responsiveness. Improving V8’s eager stack capture directly reduces the overhead of React’s JSX and `createElement` hot paths without changing React’s diagnostics.\n\n### Promise Tracking\n\nIn development, React uses Node.js’s `async_hooks` API to collect debugging information for asynchronous component execution.\n\nReact installs an `init` hook and handles `PROMISE` resources. When a relevant Promise is initialized, React constructs an `Error` and parses its stack to record where the Promise or `await` originated. This information is associated with the current Server Component owner and later used to produce async debug information and component-owner stacks.\n\nConceptually, the hot path contains:\n\n```js\ncreateHook({\n init(asyncId, type) {\n if (type === 'PROMISE') {\n const error = new Error();\n const stack = parseStackTrace(error);\n // Associate the stack with the Promise and component owner.\n }\n },\n});\n```\n\nThis callback runs synchronously for every Promise created while the RSC instrumentation is active. Promise-heavy renders can therefore execute this path thousands or tens of thousands of times.\n\nThe cost also depends on the ambient value of `Error.stackTraceLimit`. Although V8 defaults it to 10, applications and development tooling sometimes raise it to values such as 50.\n\n- The patched V8 is over twice as fast even at the default limit of 10.\n- Other frameworks, diagnostics libraries, tracing systems, and user code have similar `Error()` capture paths.\n\nReact immediately parses the captured stack in the real path. The benchmark below deliberately leaves `.stack` unread during timing to isolate the eager construction work improved by this V8 patch.\n\n## Reproduction\n\nTested with Node.js v24.18.1 at a recursive JavaScript depth of 64:\n\n\u003cdetails>\n\u003csummary>Benchmark code\u003c/summary>\n\n```js\n'use strict';\n\nconst inspector = require('node:inspector');\nconst {performance} = require('node:perf_hooks');\n\nconst limit = Number(process.argv[2]);\n\nif (limit !== 10 && limit !== 50) {\n throw new Error('Expected a stack trace limit of 10 or 50');\n}\n\nconst iterations = 100_000;\nconst samples = [];\nlet sink;\n\nError.stackTraceLimit = limit;\n\nfunction captureAtDepth(depth) {\n if (depth === 0) {\n sink = Error();\n return;\n }\n\n captureAtDepth(depth - 1);\n}\n\nfunction measure() {\n const start = performance.now();\n\n for (let i = 0; i \u003c iterations; i++) {\n captureAtDepth(64);\n }\n\n return performance.now() - start;\n}\n\n// Warm up before collecting samples.\nfor (let i = 0; i \u003c 20_000; i++) {\n captureAtDepth(64);\n}\n\nfor (let i = 0; i \u003c 5; i++) {\n samples.push(measure());\n}\n\nsamples.sort((a, b) => a - b);\n\nconsole.log(\n JSON.stringify(\n {\n node: process.version,\n v8: process.versions.v8,\n limit,\n iterations,\n samples,\n median: samples[Math.floor(samples.length / 2)],\n // Read only after timing to verify the number of captured frames.\n capturedLines: sink.stack.split('\\n').length,\n inspectorAttached: inspector.url() !== undefined,\n execArgv: process.execArgv,\n nodeOptions: process.env.NODE_OPTIONS,\n },\n null,\n 2,\n ),\n);\n```\n\n\u003c/details>\n\nRun it in fresh processes with no inspector or `NODE_OPTIONS` as that affects performance:\n\n```console\nenv -u NODE_OPTIONS node error-construction.js 10\nenv -u NODE_OPTIONS node error-construction.js 50\n```\n\nI ran two passes in opposite order, producing 10 timing samples for each configuration.\n\n## Results\n\nBoth binaries are Node.js v24.18.1 on the same machine and use the same V8 13.6 base. The only V8 version difference is the local backport suffix:\n\n| Node.js v24.18.1 | Limit 10 | Limit 50 | Limit 50 / 10 |\n|---|---:|---:|---:|\n| Unpatched, V8 `13.6.233.17-node.50` | 376.5 ms | 1,539.2 ms | 4.09× |\n| Patched, V8 `13.6.233.17-node.51` | 173.5 ms | 589.9 ms | 3.40× |\n| Improvement | 2.17× | 2.61× | |\n\nThe marginal cost per additional captured frame between limits 10 and 50 falls from approximately 291 ns to 104 ns, a 2.79× improvement.\n\nThe stack was not read during the timed section. It was read afterward to verify that the captures contained 11 and 51 lines respectively. The inspector was not attached, `process.execArgv` was empty, and `NODE_OPTIONS` was unset.\n\n## Expected outcome\n\nIt would be highly beneficial for Node.js 24 to incorporate the upstream V8 optimization so that stack-frame collection does less work while preserving existing `Error` and stack-trace behavior.\n\nThe change has been present upstream since April 2026 and has a material effect on framework development instrumentation, error capture, tracing, and other workloads that construct large numbers of `Error` objects.","author":{"url":"https://github.com/timneutkens","@type":"Person","name":"timneutkens"},"datePublished":"2026-07-31T19:14:39Z","url":"https://github.com/nodejs/node/issues/64879"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:c81d5e1e-4d9e-52c1-27b6-657479e0d147 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | AE4E:3B1A19:6D0A17:910EA9:6AED21D1 |
| html-safe-nonce | 5ec4eae2564d4432538a5daed9d46d11375a7873867e07f5a04e2aabd19a1390 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBRTRFOjNCMUExOTo2RDBBMTc6OTEwRUE5OjZBRUQyMUQxIiwidmlzaXRvcl9pZCI6IjYyMzMzOTI3MzEyNjYyOTkzIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 77702a35e2b49d85289a2028b864fa1ef985a669cb58259b9609966380e0598d |
| hovercard-subject-tag | issue:5032649556 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/64879/issue_layout |
| twitter:image | https://opengraph.githubassets.com/1edeaa91e0d93690b72c981f6f0e70fb157587498d5fd24420656bf3e51c49fd/nodejs/node/issues/64879 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/1edeaa91e0d93690b72c981f6f0e70fb157587498d5fd24420656bf3e51c49fd/nodejs/node/issues/64879 |
| og:image:alt | Summary Please backport V8 commit v8/v8@c9c0abf, “Reduce stack frame summarization costs,” to the Node.js 24 LTS release line. The backport should also include the required follow-up fix v8/v8@1a00... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | timneutkens |
| hostname | github.com |
| expected-hostname | github.com |
| None | e3e0f7f63e50c6fe879198a93a0a023a648ecc0a5e9bc3b5e033ac57172237e6 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 707a24b346ac8a82878b9c084efaa38a997660cb |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width