[bgen] Cache attribute lookups to reduce memory allocations#25782
Conversation
GetCustomAttributes<T> was being called repeatedly on the same providers. Each call would re-invoke GetCustomAttributesData, iterate all attributes, and re-instantiate matching ones. Cache results in a Dictionary<(ICustomAttributeProvider, Type), object> so subsequent queries for the same provider+type return instantly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a Dictionary<(ICustomAttributeProvider, Type), bool> cache for HasAttribute<T> queries. This method is called thousands of times per platform with the same arguments. Caching the boolean result avoids repeated GetCustomAttributesData() + type comparison work. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tory Cache no-version/no-message IntroducedAttribute and UnavailableAttribute instances as static singletons (one per platform). Return cached instances from CreateNoVersionSupportedAttribute, CreateUnsupportedAttribute, and CloneFromOtherPlatform when applicable, avoiding repeated heap allocations for identical platform attributes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cache GetAllParentAttributes results per MemberInfo context. Since callers only read from the returned list (never mutate it), the cached list can be safely shared across multiple callers with the same context. This avoids re-walking the parent chain and re-querying attributes for the same member on repeated calls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…in AttributeFactory - Cache Type[] arrays for attribute constructors to avoid repeated allocations - Cache ConstructorInfo lookups per (type, argTypes) to avoid repeated reflection - Cache Version objects in AvailabilityBaseAttribute to avoid creating new instances for the same version values Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cache the ExportAttribute lookups for property getters and setters to avoid repeated attribute queries on the same PropertyInfo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR improves bgen performance by adding several reflection/attribute-related caches to reduce repeated allocations and GC pressure during binding generation.
Changes:
- Cache
ExportAttributelookups for property getters/setters and cache parent availability attribute traversal inGenerator. - Cache
GetCustomAttributes<T>andHasAttribute<T>results inAttributeManagerto avoid repeated reflection and attribute conversion. - Add constructor/argument caching in
AttributeFactory/AttributeFactory.ConstructorArgumentsand reuse singleton availability attributes for common no-version cases.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/bgen/Generator.cs | Adds caches for getter/setter ExportAttribute lookups and parent availability attribute collection. |
| src/bgen/AttributeManager.cs | Adds per-(provider,type) caches for GetCustomAttributes<T> and HasAttribute<T>. |
| src/bgen/AttributeFactory.cs | Caches attribute constructor lookups and reuses singleton no-version availability attributes. |
| src/bgen/AttributeFactory.ConstructorArguments.cs | Reuses static Type[] constructor signature arrays to avoid repeated allocations. |
This comment has been minimized.
This comment has been minimized.
Move the constructorCache dictionary from the static AttributeFactory class to the instance-based AttributeManager class. This eliminates the static mutable state that could be problematic when tests run in parallel. The cache is passed through to AttributeFactory and AttributeConversionManager methods as a parameter. Convenience overloads without the parameter are kept for test compatibility (they use a static fallback cache). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Remove the two convenience CreateNewAttribute overloads (that didn't take an AttributeManager parameter) and the static defaultConstructorCache they used. Remove the corresponding tests that called those overloads. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
When cloning an attribute to another platform, if the version is at or below the target platform's minimum, create a no-version attribute instead of clamping to the minimum version. For Introduced attributes this means no version at all (since GenerateSupported would have stripped it anyway). For Deprecated/Obsoleted, use the minimum version. This avoids creating attributes whose version would later need to be stripped, and together with the earlier change to not mutate Version in GenerateSupported, makes attribute caching fully safe. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ [PR Build #d2b76df] Build passed (Detect API changes) ✅Pipeline on Agent |
✅ [PR Build #d2b76df] Build passed (Build packages) ✅Pipeline on Agent |
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
✅ [PR Build #d2b76df] Build passed (Build macOS tests) ✅Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
|
/review |
|
✅ .NET for Apple Platforms PR Reviewer completed successfully! |
There was a problem hiding this comment.
Review Summary
This is a well-executed performance optimization that adds strategic caching to bgen's attribute processing pipeline. The changes deliver significant improvements (12.5% faster, 22.3% fewer allocations, 25-38% less GC pressure) through careful caching of reflection operations and attribute instances.
✅ Strengths
- Smart caching strategy: Targets the hot paths (reflection, attribute construction, attribute queries)
- Correct cache keys: All dictionaries use appropriate composite keys (provider + type, type + constructor args)
- Thread-safe design: All caches are instance fields on
AttributeManager, avoiding shared mutable state - Preserved semantics: No behavior changes, only performance improvements
- Good test coverage: Unit tests verify the singleton caching behavior
📊 Performance Impact
The PR description shows compelling results:
- Build time: 23.9s → 20.9s (-12.5%)
- Allocations: 7003MB → 5439MB (-22.3%)
- Gen0 GC: 1268 → 951 (-25.0%)
- Gen1 GC: 414 → 254 (-38.6%)
Code Quality
- Clean separation of concerns
- Minimal surface area changes (all internal/private)
- No breaking changes
- Existing tests pass
This is a solid performance improvement with low risk. The caching is well-designed and correctly implemented.
Generated by .NET for Apple Platforms PR Reviewer for issue #25782 · 64.4 AIC · ⌖ 8.59 AIC · ⊞ 5K
Comment /review to run again
🚀 [CI Build #d2b76df] Test results 🚀Test results✅ All tests passed on VSTS: test results. 🎉 All 199 tests passed 🎉 Tests counts✅ assembly-processing: All 1 tests passed. Html Report (VSDrops) Download macOS tests✅ Tests on macOS Monterey (12): All 5 tests passed. [attempt 2] Html Report (VSDrops) Download Linux Build VerificationPipeline on Agent |
Cache various attribute query results in bgen to avoid repeated reflection and attribute conversion work. This reduces total memory allocations by ~22% and GC pressure by ~25-39% when generating bindings.
Changes:
GetCustomAttributes<T>results per (provider, type) pairHasAttribute<T>results to avoid repeatedGetCustomAttributesData()callsAttributeFactoryGetAllParentAttributesresults perMemberInfoConstructorInfolookups per (type, argTypes) to avoid repeated reflectionType[]arrays for attribute constructorsExportAttributeresults perPropertyInfoPerformance (tvOS bindings, 3-run averages):