Skip to content

[Repo Assist] perf: cache reflection lookups and avoid double seq materialisation#378

Merged
sergey-tihon merged 2 commits intomasterfrom
repo-assist/perf-cache-reflection-20260413-5ed0467a62284450
Apr 14, 2026
Merged

[Repo Assist] perf: cache reflection lookups and avoid double seq materialisation#378
sergey-tihon merged 2 commits intomasterfrom
repo-assist/perf-cache-reflection-20260413-5ed0467a62284450

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

🤖 This PR was created by Repo Assist, an automated AI assistant.

Summary

Four small, targeted performance improvements to the design-time compiler and the runtime helpers. Each reduces repeated work when processing large schemas (e.g., the Stripe API or any schema with hundreds of types and thousands of properties).

Changes

Location Change
RuntimeHelpers.fs Cache JsonPropertyNameAttribute constructor — looked up once per property with a non-identity name during schema compilation
DefinitionCompiler.fs Cache obj.ToString MethodInfo per DefinitionCompiler instance instead of once per compiled object type
RuntimeHelpers.fs Cache Option<T>.Value PropertyInfo per concrete option type in unwrapFSharpOption, avoiding a GetProperty call on every optional form/query parameter serialisation
DefinitionCompiler.fs Materialise schemaObjProperties to a list once (schemaObjPropertiesList) instead of twice (once for the members loop, once for the constructor zip)

Details

JsonPropertyNameAttribute constructor caching

getPropertyNameAttribute is called once per object property whose serialised name differs from its F# name. For a large schema with thousands of such properties, typeof<JsonPropertyNameAttribute>.GetConstructor [| typeof<string> |] was invoked on every call. This is now cached as a module-level let binding.

obj.ToString MethodInfo caching

compileNewObject calls typeof<obj>.GetMethod("ToString", [||]) to define the ToString override for each generated type. For a schema with hundreds of object types this was done repeatedly. The MethodInfo is now captured once as a class-level binding in DefinitionCompiler.

Option<T>.Value PropertyInfo caching

unwrapFSharpOption is on the hot path for every optional form/query parameter serialisation at runtime. It previously called ty.GetProperty("Value") on each invocation. The PropertyInfo is now cached in a ConcurrentDictionary<Type, PropertyInfo> keyed by the concrete option type, so subsequent calls for the same option<T> type skip the reflection lookup entirely.

Avoid double sequence materialisation

In compileNewObject, schemaObjProperties (a seq<KeyValuePair<...>>) was materialised with List.ofSeq twice — once for the members mapping loop, and again for List.zip ... members when building the constructor parameters. Saving the first materialisation as schemaObjPropertiesList eliminates the redundant traversal.

Test Status

✅ Build: succeeded (0 errors, pre-existing warnings only)
✅ Unit tests: 268 passed, 0 failed, 1 skipped
✅ Format check: fantomas --check passed on changed files

Integration/provider tests require a running Swashbuckle test server; the changes are in design-time compilation and runtime serialisation helpers that have no schema-specific behaviour.

Generated by 🌈 Repo Assist, see workflow run. Learn more.

Generated by 🌈 Repo Assist, see workflow run. Learn more.

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@97143ac59cb3a13ef2a77581f929f06719c7402a

- Cache JsonPropertyNameAttribute constructor in getPropertyNameAttribute
  (called once per property with a non-identity name during schema compilation).
- Cache obj.ToString MethodInfo in DefinitionCompiler; one lookup per
  compiler instance rather than one per compiled object type.
- Cache Option<T>.Value PropertyInfo in unwrapFSharpOption; avoids a
  GetProperty call on every optional form/query parameter serialisation.
- Materialise schemaObjProperties to a list once (schemaObjPropertiesList)
  instead of twice (once for the members loop, once for the ctor zip).

For large schemas such as the Stripe API (hundreds of types, thousands of
properties) these micro-optimisations reduce GC allocation and repeated
reflection overhead during the design-time compilation phase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sergey-tihon sergey-tihon marked this pull request as ready for review April 14, 2026 04:29
Copilot AI review requested due to automatic review settings April 14, 2026 04:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR applies a set of targeted performance optimizations focused on reducing repeated reflection work and avoiding redundant sequence materialization during schema compilation and runtime form/query serialization.

Changes:

  • Cache JsonPropertyNameAttribute(string) constructor info in RuntimeHelpers to avoid repeated reflection during design-time compilation.
  • Cache Option<T>.Value PropertyInfo per concrete option type to reduce reflection overhead when serializing optional form/query parameters.
  • Cache obj.ToString() MethodInfo per DefinitionCompiler instance and avoid double materialization of schemaObjProperties by reusing a single list.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/SwaggerProvider.Runtime/RuntimeHelpers.fs Adds reflection caches for JsonPropertyNameAttribute constructor and Option<T>.Value PropertyInfo to reduce hot-path overhead.
src/SwaggerProvider.DesignTime/DefinitionCompiler.fs Caches obj.ToString() MethodInfo and reuses a single materialized list of schema properties to avoid redundant work during type generation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sergey-tihon sergey-tihon merged commit f78ff86 into master Apr 14, 2026
6 checks passed
@sergey-tihon sergey-tihon deleted the repo-assist/perf-cache-reflection-20260413-5ed0467a62284450 branch April 14, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants