[Repo Assist] perf: cache reflection lookups and avoid double seq materialisation#378
Merged
sergey-tihon merged 2 commits intomasterfrom Apr 14, 2026
Conversation
- 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>
Contributor
There was a problem hiding this comment.
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 inRuntimeHelpersto avoid repeated reflection during design-time compilation. - Cache
Option<T>.ValuePropertyInfoper concrete option type to reduce reflection overhead when serializing optional form/query parameters. - Cache
obj.ToString()MethodInfoperDefinitionCompilerinstance and avoid double materialization ofschemaObjPropertiesby 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.
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.
🤖 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
RuntimeHelpers.fsJsonPropertyNameAttributeconstructor — looked up once per property with a non-identity name during schema compilationDefinitionCompiler.fsobj.ToStringMethodInfoperDefinitionCompilerinstance instead of once per compiled object typeRuntimeHelpers.fsOption<T>.ValuePropertyInfoper concrete option type inunwrapFSharpOption, avoiding aGetPropertycall on every optional form/query parameter serialisationDefinitionCompiler.fsschemaObjPropertiesto a list once (schemaObjPropertiesList) instead of twice (once for the members loop, once for the constructorzip)Details
JsonPropertyNameAttributeconstructor cachinggetPropertyNameAttributeis 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-levelletbinding.obj.ToStringMethodInfocachingcompileNewObjectcallstypeof<obj>.GetMethod("ToString", [||])to define theToStringoverride for each generated type. For a schema with hundreds of object types this was done repeatedly. TheMethodInfois now captured once as a class-level binding inDefinitionCompiler.Option<T>.ValuePropertyInfocachingunwrapFSharpOptionis on the hot path for every optional form/query parameter serialisation at runtime. It previously calledty.GetProperty("Value")on each invocation. ThePropertyInfois now cached in aConcurrentDictionary<Type, PropertyInfo>keyed by the concrete option type, so subsequent calls for the sameoption<T>type skip the reflection lookup entirely.Avoid double sequence materialisation
In
compileNewObject,schemaObjProperties(aseq<KeyValuePair<...>>) was materialised withList.ofSeqtwice — once for themembersmapping loop, and again forList.zip ... memberswhen building the constructor parameters. Saving the first materialisation asschemaObjPropertiesListeliminates the redundant traversal.Test Status
✅ Build: succeeded (0 errors, pre-existing warnings only)
✅ Unit tests: 268 passed, 0 failed, 1 skipped
✅ Format check:
fantomas --checkpassed on changed filesIntegration/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.