Skip to content

perf: cache resolved instance functions to avoid repeated lookups in deep hierarchies#108

Open
hectorhdzg wants to merge 2 commits into
microsoft:mainfrom
hectorhdzg:fix/deep-hierarchy-perf-cache
Open

perf: cache resolved instance functions to avoid repeated lookups in deep hierarchies#108
hectorhdzg wants to merge 2 commits into
microsoft:mainfrom
hectorhdzg:fix/deep-hierarchy-perf-cache

Conversation

@hectorhdzg
Copy link
Copy Markdown
Member

When the instance shortcut optimization cannot be applied (frozen/readonly targets, Proxy objects, or instances that already own the property), every dynamic method call previously re-entered _getInstFunc with repeated property lookups on every invocation.

This change adds a per-proxy WeakMap cache that stores the resolved instance function after first resolution. Subsequent calls to the same proxy for the same instance return the cached function directly, eliminating the _getInstFunc overhead entirely.

Additionally, the prototype chain walk inside _getInstFunc is now guarded behind the canAddInst check, avoiding unnecessary visited-array allocation when the instance already owns the property.

For environments without WeakMap (e.g. IE8), the behavior gracefully falls back to the existing code path with no regression.

Impact: Reduces per-call overhead from O(property lookups) to O(1) WeakMap.get for all cases where the shortcut optimization cannot be applied, particularly benefiting deep inheritance hierarchies (10+ levels).

Copy link
Copy Markdown
Collaborator

@MSNev MSNev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No!

DynamicProto MUST work in ES5 and all of the WeakXXXX classes don't exist in ES5.

@hectorhdzg hectorhdzg force-pushed the fix/deep-hierarchy-perf-cache branch from 3d8a573 to 846ea21 Compare May 18, 2026 22:10
@hectorhdzg hectorhdzg changed the title perf: add WeakMap cache for deep hierarchy method resolution perf: cache resolved instance functions to avoid repeated lookups in deep hierarchies May 18, 2026
Comment thread lib/src/DynamicProto.ts Outdated
Copy link
Copy Markdown
Collaborator

@MSNev MSNev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments

Comment thread lib/src/DynamicProto.ts
// instFuncTable is created with objCreate(null) during construction and is not frozen
// even when the instance itself is frozen (Object.freeze is shallow).
if (instFuncTable) {
instFuncTable[cacheKey] = instFunc;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there is one thought around this PR and the class one, in that by caching the "lookups" this would break the previous capability (but not sure if we actual use this today) of dynamically adjusting to change on base classes.

Generally, we build "on-top" of the items (extending / changing features) for new base classes, so this caching should be fine as I can't currently thing of any specific issues...

But we should probably test locally before releasing an updated version.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 new re-registration tests

Re-calling dynamicProto on the same instance updates the resolved function (not stale)
Re-registration on one instance doesn't affect other instances
Child class with setInstFuncs: true — base proxy is early-bound (re-registration doesn't propagate)
Child class with setInstFuncs: false — base proxy is late-bound through the dynamic proxy (re-registration IS visible)
Multiple dynamicProto calls in a single constructor — last registration wins

@hectorhdzg hectorhdzg force-pushed the fix/deep-hierarchy-perf-cache branch from 846ea21 to 47efc2f Compare May 18, 2026 22:31
When the instance shortcut optimization cannot be applied (frozen/readonly
targets, Proxy objects, or when the instance already owns the property), every
dynamic method call previously re-entered _getInstFunc with repeated property
lookups on every invocation.

This change adds a per-instance resolved-function cache stored directly in the
existing instFuncTable object (keyed by className + funcName). Since
instFuncTable is created with objCreate(null) during construction and
Object.freeze is shallow, it remains writable even when the instance itself is
frozen. Subsequent calls to the same proxy for the same instance return the
cached function directly, eliminating the _getInstFunc overhead entirely.

Additionally, the prototype chain walk inside _getInstFunc is now guarded
behind the canAddInst check, avoiding unnecessary visited-array allocation
when the instance already owns the property.

Fully ES5 compatible - no WeakMap or other ES6+ APIs required.

Impact: Reduces per-call overhead from O(property lookups) to O(1) cache hit
for all cases where the shortcut optimization cannot be applied, particularly
benefiting deep inheritance hierarchies (10+ levels).
@hectorhdzg hectorhdzg force-pushed the fix/deep-hierarchy-perf-cache branch from 47efc2f to 2ac41b2 Compare May 19, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants