Skip to content

Commit e60615c

Browse files
[Repo Assist] perf: O(1) assembly-name dictionary lookup in convTypeRef (#493)
🤖 *This PR was created by Repo Assist, an automated AI assistant.* ## Summary Replaces the O(n) `Seq.tryFind` scan in `convTypeRef`'s `bestGuess` step with O(1) dictionary lookups against the existing assembly-name tables. ## Root Cause / Motivation `convTypeRef` translates a design-time `Type` to its target-context equivalent (or vice-versa). For a type `t` that hasn't been seen before, the previous code did: ```fsharp let bestGuess = asms |> Seq.tryFind(fun a -> a.FullName = t.Assembly.FullName) |> Option.bind(fun a -> tryGetTypeFromAssembly toTgt t.Assembly.FullName fullName a) ``` This is an O(n) scan through all target/source assemblies for every unique type. With 100–200 reference assemblies and hundreds or thousands of distinct types in a large generated schema, this can add meaningful latency during TP warm-up. `ProvidedTypesContext` already maintains two name-indexed `ConcurrentDictionary`s: - `targetAssembliesTable_` keyed by assembly simple name (→ `Choice<Assembly, exn>`) - `sourceAssembliesTable_` keyed by assembly simple name (→ `Assembly`) ## Fix Use `t.Assembly.GetName().Name` as a dictionary key for O(1) look-up: ```fsharp let asmSimpleName = t.Assembly.GetName().Name let bestGuess = if toTgt then let table = getTargetAssembliesTable() match table.TryGetValue(asmSimpleName) with | true, Choice1Of2 asm -> tryGetTypeFromAssembly toTgt t.Assembly.FullName fullName asm | _ -> None else let table = getSourceAssembliesTable() match table.TryGetValue(asmSimpleName) with | true, asm -> tryGetTypeFromAssembly toTgt t.Assembly.FullName fullName asm | _ -> None ``` The linear scan fallback (`loop`) is fully preserved for the rare case where the assembly isn't in the table (e.g. type forwarding across assembly boundaries). The original `getTargetAssemblies()`/`getSourceAssemblies()` call is also preserved to populate `asms` for the fallback. A new `getSourceAssembliesTable()` helper is added (mirrors the existing `getTargetAssembliesTable()`). ## Trade-offs - Simple name lookup may theoretically match the wrong assembly if two assemblies with the same simple name but different versions are present simultaneously. In practice this does not occur in type provider reference sets. The fallback loop would still catch it. - Per-type translation is already memoised in `typeTableFwd`/`typeTableBwd`, so each unique type is resolved at most once; the benefit is most visible during initial TP warm-up with large schemas. ## Test Status ✅ All 126 tests pass (`dotnet test tests/FSharp.TypeProviders.SDK.Tests.fsproj -c Release`) > Generated by 🌈 Repo Assist at [Run](https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/23826131948). [Learn more](https://github.com/githubnext/agentics/blob/main/docs/repo-assist.md). > Generated by 🌈 Repo Assist at [{run-started}](https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/23826131948). [Learn more](https://github.com/githubnext/agentics/blob/main/docs/repo-assist.md). > > To install this [agentic workflow](https://github.com/githubnext/agentics/tree/1f672aef974f4246124860fc532f82fe8a93a57e/workflows/repo-assist.md), run > ``` > gh aw add githubnext/agentics@1f672ae > ``` <!-- gh-aw-agentic-workflow: Repo Assist, engine: copilot, model: auto, id: 23826131948, workflow_id: repo-assist, run: https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/23826131948 --> <!-- 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>
1 parent d1b018c commit e60615c

1 file changed

Lines changed: 18 additions & 4 deletions

File tree

src/ProvidedTypes.fs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9197,6 +9197,8 @@ namespace ProviderImplementation.ProvidedTypes
91979197
) |> ignore
91989198
sourceAssemblies_
91999199

9200+
let getSourceAssembliesTable() = getSourceAssemblies() |> ignore; sourceAssembliesTable_
9201+
92009202
/// When translating quotations, Expr.Var's are translated to new variable respecting reference equality.
92019203
let varTableFwd = Dictionary<Var, Var>()
92029204
let varTableBwd = Dictionary<Var, Var>()
@@ -9255,10 +9257,22 @@ namespace ProviderImplementation.ProvidedTypes
92559257
| _ ->
92569258
let asms = (if toTgt then getTargetAssemblies() else getSourceAssemblies())
92579259
let fullName = fixName t.FullName
9258-
9259-
let bestGuess =
9260-
asms |> Seq.tryFind(fun a -> a.FullName = t.Assembly.FullName)
9261-
|> Option.bind(fun a -> tryGetTypeFromAssembly toTgt t.Assembly.FullName fullName a)
9260+
let asmSimpleName = t.Assembly.GetName().Name
9261+
9262+
// Use the assembly-name dictionary (O(1)) rather than a sequential scan.
9263+
// Fall through to None if the assembly isn't in the table, which lets the
9264+
// linear fallback below handle cross-assembly type forwarding edge cases.
9265+
let bestGuess =
9266+
if toTgt then
9267+
let table = getTargetAssembliesTable()
9268+
match table.TryGetValue(asmSimpleName) with
9269+
| true, Choice1Of2 asm -> tryGetTypeFromAssembly toTgt t.Assembly.FullName fullName asm
9270+
| _ -> None
9271+
else
9272+
let table = getSourceAssembliesTable()
9273+
match table.TryGetValue(asmSimpleName) with
9274+
| true, asm -> tryGetTypeFromAssembly toTgt t.Assembly.FullName fullName asm
9275+
| _ -> None
92629276

92639277
match bestGuess with
92649278
| Some (newT, canSave) ->

0 commit comments

Comments
 (0)