Skip to content

Index pre-generated types per assembly to avoid O(n) ExportedTypes scans (#189)#192

Merged
jeremydmiller merged 1 commit intomainfrom
fix-189-indexed-pregenerated-type-lookup
Apr 27, 2026
Merged

Index pre-generated types per assembly to avoid O(n) ExportedTypes scans (#189)#192
jeremydmiller merged 1 commit intomainfrom
fix-189-indexed-pregenerated-type-lookup

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Summary

CodeGenerationExtensions.FindPreGeneratedType ran assembly.ExportedTypes.FirstOrDefault on every call, making each lookup O(n) in the entry assembly's exported-type count.

Static-mode consumers (Marten, Wolverine) call the helper once per code file — storage providers, query handlers, projection types, event-store insert ops — so an app with 50 code files and a 1000-type entry assembly does ~50,000 type comparisons during cold start.

This PR caches an indexed dictionary per Assembly behind a ConditionalWeakTable (so the hash isn't rooted — plugin / unloadable AssemblyLoadContext scenarios still work). First lookup builds the index in O(n); every subsequent lookup is O(1).

Bumps JasperFx to 1.27.0.

Closes #189.

Test plan

  • New CodeGenerationExtensionsTests — 4 facts, all pass:
    • returns a known exported type
    • returns null for an unknown name
    • is repeatable against the same assembly (cache transparency)
    • keys correctly across distinct assemblies
  • Full CodegenTests suite (305 tests) green — no regressions

Notes

  • Pure internal optimization; no public API change.
  • Companion to the Marten 9.0 cold-start umbrella issue (JasperFx/marten#4294).

🤖 Generated with Claude Code

…dTypes scans

CodeGenerationExtensions.FindPreGeneratedType ran assembly.ExportedTypes
.FirstOrDefault on every call, making each lookup O(n) in the entry assembly's
exported-type count. Static-mode consumers (Marten, Wolverine) call the helper
once per code file - storage providers, query handlers, projection types,
event-store insert ops - so an app with 50 code files and a 1000-type entry
assembly does ~50,000 type comparisons during cold start.

Cache an indexed dictionary per assembly behind a ConditionalWeakTable so the
hash isn't rooted (plugin / unloadable AssemblyLoadContext scenarios still
work). First lookup builds the index in O(n); every subsequent lookup is O(1).

Bump JasperFx to 1.27.0.

Closes #189.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 418ba06 into main Apr 27, 2026
1 check passed
@jeremydmiller jeremydmiller deleted the fix-189-indexed-pregenerated-type-lookup branch April 27, 2026 21:49
jeremydmiller added a commit to JasperFx/marten that referenced this pull request Apr 27, 2026
…ocations (#4295)

JasperFx 1.27.0 (JasperFx/jasperfx#192) makes
CodeGenerationExtensions.FindPreGeneratedType O(1) per call by caching an
indexed dictionary per Assembly. Bumps the pin so static-mode cold start
stops paying O(ExportedTypes) per ICodeFile.AttachTypes call.

Two small Marten-side fixes alongside:

* QuickEventAppender previously allocated a new Queue<long> per stream
  inside the foreach over WorkTracker.Streams, even though the quick-append
  path never reads from it (only applyRichMetadata dequeues; applyQuick
  doesn't touch it). Hoist a single throwaway instance out of the loop so
  bulk appends stop paying one allocation per stream.

* DocumentSessionBase.operationDocumentTypes() did
  Operations().Select(...).Where(...).Distinct(), enumerating Operations()
  twice and chaining a fresh enumerator stack on every SaveChanges. Replace
  with a single-pass HashSet<Type> walk. Same observable behavior, fewer
  intermediate allocations on the hot save path.

Companion to the Marten 9.0 cold-start umbrella issue (#4294); the
non-breaking subset that didn't have to wait for 9.0.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cold-start: index pre-generated types per assembly to avoid repeated O(n) ExportedTypes scans

1 participant