MenuService: reduce menu rebuild overhead through caching#324358
Open
n-gist wants to merge 6 commits into
Open
MenuService: reduce menu rebuild overhead through caching#324358n-gist wants to merge 6 commits into
n-gist wants to merge 6 commits into
Conversation
…uInfo_sort cache stability
Contributor
There was a problem hiding this comment.
Pull request overview
This PR reduces MenuService/MenuRegistry overhead during frequent menu rebuilds (e.g. TreeView scrolling) by eliminating redundant sorting, context-key collection, and array allocations. It partially addresses #324355. The approach layers several identity/immutability-based caches on top of the menu infrastructure and, as a prerequisite, tightens the immutability contracts of menu item interfaces.
Changes:
- Caches stable, frozen menu-item arrays in
MenuRegistry.getMenuItems()(invalidated on registration changes), and caches_sortresults andContextKeyExpression.keys()results inMenuInfoSnapshot/MenuInfoviaWeakMaps keyed by object identity. - Removes the duplicate
refresh()in theMenuInfoconstructor (the baseMenuInfoSnapshotalready refreshes). - Makes sort-relevant menu properties
readonly(command,group,order,title,ILocalizedString.value/original) to guarantee cache correctness, and refactorsExternalTerminalContributionto re-register menu items instead of mutating a registered command title (also fixing a Windows-variant that never switched back).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/vs/platform/actions/common/menuService.ts |
Adds context-key and sorted-menu-item WeakMap caches; removes duplicate refresh(); _sort now returns/accepts readonly arrays. |
src/vs/platform/actions/common/actions.ts |
Caches frozen getMenuItems results with proper invalidation; makes sort-relevant IMenuItem/ISubmenuItem props readonly. |
src/vs/platform/action/common/action.ts |
Makes ILocalizedString.value/original and ICommandAction.title readonly. |
src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts |
Replaces title mutation with menu re-registration keyed by a variant enum; fixes non-reverting Windows title. |
src/vs/workbench/services/actions/common/menusExtensionPoint.ts |
Refactors item construction to compute group/order/when up-front; behavior-equivalent. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Partially addresses #324355
Overview
This PR reduces MenuService overhead during TreeView scrolling and other components item rendering by eliminating repeated work performed while menus are rebuilt for visible items.
Performance investigation #324355 showed that menu processing is executed multiple times for the same element during a single rendering cycle, causing identical menu structures to be repeatedly sorted and analyzed even though neither menu registrations nor context key expressions change during scrolling. This PR focuses on removing that redundant work.
_sort_collectContextKeysAndSubmenuIdswas responsible for a substantial portion of the frame time. In addition, context key expression processing continuously allocated new arrays during menu reconstruction.
The changes in this PR focus on removing these repeated computations and allocations while preserving correctness through immutability guarantees and cache-safe data structures.
Changes (per commit)
1. MenuInfo: Remove duplicate refresh during initialization
MenuInfoextendsMenuInfoSnapshot, and both constructors ended up triggeringrefresh()during initialization.This change removes the duplicate refresh call, avoiding redundant menu processing during construction.
2. Make IMenuItem and ISubmenuItem properties immutable
Caching sorted menu structures requires stable sorting inputs.
The properties involved in menu ordering were made
readonlyto guarantee sorting cache correctness. More properties could potentially be made immutable in the future, but this PR limits changes to the fields required for sorting stability. For example, thewhenproperty is still constructed and modified in multiple places before command registration and would require a broader refactoring before the entire interface can become immutable.During this work,
ExternalTerminalContributionwas identified as the only place in the codebase mutating a registered menu command title. The mutation was replaced with command re-registration, preserving immutability assumptions.This also fixes an existing issue where the terminal command title could switch to the Windows-specific variant but never switch back when the configuration changed.
3. MenuRegistry: cache stable readonly menu item arrays
MenuRegistry.getMenuItems()previously produced new array instances even when menu contents remained unchanged.This change introduces stable
readonlyarrays that are reused while the underlying menu structure remains unchanged.Since menu registrations typically change very infrequently compared to how often menus are queried, reusing stable arrays is beneficial on its own and also enables additional optimizations built on top of array identity.
4. MenuInfo: cache sorted menu item arrays
As of the previous change,
MenuRegistrynow returns stable menu item arrays.This makes it possible to cache
MenuInfo._sortresults and reuse them whenever the source menu array instance is unchanged, relying on the fact that all properties participating in sorting are now immutable.This removes repeated sorting work and the associated allocations during menu reconstruction.
Caching was initially implemented at the
MenuInfoinstance level, but this proved ineffective becauseMenuInfoinstances themselves are not reused. As a result, a globalWeakMapcache keyed by the stable source array identity was adopted as the most effective approach.5. MenuService: cache ContextKeyExpression.keys() results
Profiling showed that
ContextKeyExpression.keys()is the primary contributor to the cost ofMenuInfoSnapshot._collectContextKeysAndSubmenuIds.Every call recursively created new arrays even though context key expressions are immutable and frequently reused.
All expression implementations were reviewed to verify that the contents and ordering of
keys()results remain stable for the lifetime of an expression instance.While expression classes could potentially cache their own
keys()results internally, this PR intentionally avoids changing expression implementations and instead introduces caching at theMenuServicelevel using aWeakMap.Benefits:
keys()allocationskeys()processing from performance tracesNotes
The optimizations intentionally rely on object identity and immutability guarantees rather than invalidation logic.
Where caching was introduced, prerequisites were implemented and inspected first to guarantee correctness and prevent stale results.
Performance Results
Tests were performed incrementally after each optimization commit by rapidly scrolling up and down for approximately 10 seconds in an OSS debug build.
Measurement code
Counts renderElement calls and outputs result on 2 seconds of idling.Measurement results
Summary
Average renderElement duration:
This represents roughly a 35–40% reduction in renderElement execution time during scrolling, and likely benefits other menu-heavy scenarios as well.
Notes
The OSS build does not exhibit the same severity of performance issues described in the #324355. In particular,
_sortis not always visible as a significant contributor in OSS traces, even before these optimizations, so commit 4 may be somewhat underrepresented in the measurements above.However, in the user setup that motivated the investigation, animation frame execution could reach 100–200 ms, with
_sortand_collectContextKeysAndSubmenuIdstogether contributing up to approximately 65% of the total frame cost.Because these optimizations directly target those hot paths and eliminate a large amount of repeated work and allocations, the expected impact should scale more favorably in scenarios similar to the linked issue than is reflected by the OSS measurements alone.