Skip to content

[Repo Assist] perf: avoid O(n²) allocations in ILMethodDefs; use lazy caches in TargetTypeDefinition member lookups#497

Merged
dsyme merged 2 commits intomasterfrom
repo-assist/perf-improve-targettype-lookups-20260403-c3ef47a-c6d0b490e2ae80ae
Apr 3, 2026
Merged

[Repo Assist] perf: avoid O(n²) allocations in ILMethodDefs; use lazy caches in TargetTypeDefinition member lookups#497
dsyme merged 2 commits intomasterfrom
repo-assist/perf-improve-targettype-lookups-20260403-c3ef47a-c6d0b490e2ae80ae

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented Apr 3, 2026

🤖 This is an automated PR from Repo Assist.

Summary

Two complementary performance improvements to TargetTypeDefinition and the internal ILMethodDefs name-index.

1. O(1)-per-entry method-name index construction (ILMethodDefs.lmap)

Before: the lazy name→methods dictionary was built by prepending each new entry to an existing array:

m.[key] <- Array.append [| y |] lmpak   // O(n) per insertion

For a type with N methods sharing a name, this allocates arrays of sizes 1, 2, 3 … N — O(N²) total elements. The BCL contains types (e.g. String, Convert) with dozens of overloads per name, making this measurable.

After: a ResizeArray accumulator collects entries per name in O(1) each, then a single ToArray() call per bucket produces the final arrays — O(N) total elements.

2. Lazy-cache reuse in GetField, GetPropertyImpl, GetEvent

Before: each call to GetField/GetPropertyImpl/GetEvent scanned the raw ILFieldDef/ILPropertyDef/ILEventDef arrays and called the txIL* factory to create a brand-new wrapper object:

inp.Fields.Entries |> Array.tryPick (fun p -> if p.Name = name then Some (txILFieldDef this p) else None)

This created a new allocation on every call even when fieldDefs/propDefs/eventDefs lazy arrays were already fully computed (e.g. after a preceding GetFields(bindingFlags) call).

After: the methods call Force() on the already-computed lazy arrays and filter by name. When the caches are warm (the common case during compilation) zero new wrappers are allocated, and callers get consistent object identity across GetFields/GetField pairs.

Test Status

✅ 126/126 tests pass (dotnet test tests/FSharp.TypeProviders.SDK.Tests.fsproj).

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

To install this agentic workflow, run

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

…getTypeDefinition member lookups

ILMethodDefs.lmap was building per-name buckets with repeated Array.append
prepends — O(n²) total allocations for a type with N methods. Switch to a
ResizeArray accumulator that produces each bucket array in a single ToArray call.

TargetTypeDefinition.GetField / GetPropertyImpl / GetEvent previously bypassed
the per-type lazy caches (fieldDefs / propDefs / eventDefs) and called the tx*
wrapper factories on every invocation.  Using Force() on the already-computed
lazy arrays returns the same cached wrapper objects, avoids redundant allocations
when the arrays are already warm, and guarantees consistent object identity
across GetFields/GetField call pairs.

126/126 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsyme dsyme marked this pull request as ready for review April 3, 2026 12:49
@dsyme dsyme merged commit 6c8ab20 into master Apr 3, 2026
2 checks passed
github-actions Bot added a commit that referenced this pull request Apr 7, 2026
- Performance: O(1) convTypeRef assembly-name lookup (#493)
- Performance: O(N) ILMethodDefs index construction; lazy caches in TargetTypeDefinition (#497)
- Refactor: save-based caching for GetField/GetEvent/GetNestedType; Dictionary in ILNestedExportedTypesAndForwarders (#498)
- CI: NuGet and FAKE build caching (#495)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dsyme pushed a commit that referenced this pull request Apr 7, 2026
🤖 *This PR was created by [Repo
Assist](https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/24058266578).*

## Summary

Bumps the version to **8.5.0** by adding an entry to `RELEASE_NOTES.md`
covering all changes merged since 8.4.0:

- **Performance**: O(1) assembly-name dictionary lookup in `convTypeRef`
(#493)
- **Performance**: Avoid O(n²) allocations in `ILMethodDefs` name-index
construction; reuse lazy caches in `TargetTypeDefinition` for
`GetField`/`GetPropertyImpl`/`GetEvent` (#497)
- **Refactor**: `save`-based caching for
`GetField`/`GetEvent`/`GetNestedType` on `ProvidedTypeDefinition`;
`Dictionary` in `ILNestedExportedTypesAndForwarders` (#498)
- **CI**: NuGet and FAKE build caching (#495)

No code changes — RELEASE_NOTES.md only.

## Test Status

✅ 126/126 tests pass (`dotnet test
tests/FSharp.TypeProviders.SDK.Tests.fsproj -c Release`).




> Generated by 🌈 Repo Assist, see [workflow
run](https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/24058266578).
[Learn
more](https://github.com/githubnext/agentics/blob/main/docs/repo-assist.md).
>
> To install this [agentic
workflow](https://github.com/githubnext/agentics/blob/7ee2b60744abf71b985bead4599640f165edcd93/workflows/repo-assist.md),
run
> ```
> gh aw add
githubnext/agentics@7ee2b60
> ```

<!-- gh-aw-agentic-workflow: Repo Assist, engine: copilot, model: auto,
id: 24058266578, workflow_id: repo-assist, run:
https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/24058266578
-->

<!-- gh-aw-workflow-id: repo-assist -->

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
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.

1 participant