[Perf Tracks]: Clear potentially large measures
Project: facebook/react | Language: JavaScript | Concepts: memory-management, observability | Difficulty: mid
Link: https://github.com/reactjs/react/pull/34803
The problem
The React performance tracking code used performance.measure() to record timing and details about component renders, errors, and other events. However, these measures were not being cleared after use, causing them to accumulate in the browser's performance entry buffer. Over time, especially in development mode with frequent renders and large props, this could lead to excessive memory usage in Chromium-based browsers.
Why it's non-trivial
A naive fix might simply ignore the accumulation, or only clear measures globally, which could remove entries still needed for debugging or other events. The solution needs to clear only the relevant measures, at the right time, and only in development mode, so as not to interfere with production performance or debugging workflows.
The approach
The PR systematically adds calls to performance.clearMeasures() immediately after each relevant performance.measure() call, scoped to the specific measure name. This ensures only the intended entries are removed, preventing unbounded memory growth.
Example 1: Clearing after measuring a component render
+ const measureName = '\u200b' + entryName;
if (__DEV__ && debugTask) {
...
- performance.measure.bind(performance, '\u200b' + entryName, {
+ performance.measure.bind(performance, measureName, {
...
}),
);
+ performance.clearMeasures(measureName);
Here, after measuring the component render, the code immediately clears the measure by name. This targets only the just-recorded entry.
Example 2: Handling other events (aborted, errored, IO, etc.)
+ performance.measure(measureName, {
...
});
+ performance.clearMeasures(measureName);
The same pattern is applied to other logging functions, such as for aborted renders, errors, and IO events. The measure is always cleared right after recording.
Example 3: Consistency across files
In packages/react-reconciler/src/ReactFiberPerformanceTrack.js, the same logic is applied:
+ performance.clearMeasures(trigger);
After measuring a trigger, the measure is cleared. This pattern is repeated for all relevant performance events.
Example 4: Only in development mode
All these changes are guarded by if (__DEV__), ensuring that production builds are unaffected:
if (__DEV__ && debugTask) {
...
+ performance.clearMeasures(measureName);
} else {
...
}
This prevents unnecessary performance overhead or loss of debugging information in production.
From the review thread
No notable review discussion on this PR.
Takeaways
- Always clean up performance or diagnostic resources after use to prevent unbounded memory growth, especially in development tools.
- Scope cleanup to the specific resource (e.g., measure name) rather than using global clears, to avoid interfering with unrelated diagnostics.
- Restrict development-only diagnostics and cleanup to development builds to avoid any production impact.