Skip to content

fix(perf): Eliminate ConcatIterator CPU waste in DI resolution#213

Closed
anthony-keller wants to merge 2 commits intoEFNext:v3from
autoguru-au:fix/concat-iterator-perf
Closed

fix(perf): Eliminate ConcatIterator CPU waste in DI resolution#213
anthony-keller wants to merge 2 commits intoEFNext:v3from
autoguru-au:fix/concat-iterator-perf

Conversation

@anthony-keller
Copy link

Summary

  • Replace IEnumerable<T> fields with List<T> in TriggersOptionExtension to eliminate deeply nested ConcatIterator chains created by repeated .Concat() calls during trigger registration
  • Cache GetServiceProviderHashCode() result to avoid re-computing on every EF Core ServiceProviderCache lookup
  • Optimize ShouldUseSameServiceProvider with scalar and count-based fast paths before falling back to SequenceEqual
  • Use List.Count property (O(1)) instead of LINQ .Count() extension (O(N)) in PopulateDebugInfo

Context

CPU profiling showed 50%+ of Self CPU burned in ConcatIterator<T>.MoveNext() during DI service resolution. Every request triggers EF Core's ServiceProviderCache to enumerate the _triggers and _triggerTypes fields, which were lazy IEnumerable<T> chains built via .Concat() — one nesting level per trigger registration.

Test plan

  • dotnet build — 0 errors
  • dotnet test — all 241 tests pass (166 core + 38 extensions + 22 transactions + 15 integration)
  • No new tests needed — changes are internal implementation details with unchanged public API

🤖 Generated with Claude Code

anthony-keller and others added 2 commits March 8, 2026 11:45
Add CLAUDE.md to provide guidance for Claude Code when working with this repository. The file documents project overview, build and test commands, multi-version build system (V1/V2/V3), global build settings, architecture and core flow of the trigger library, key internal types, and test project layout. Intended as a quick reference for building, testing, and understanding trigger execution and project structure.
…r CPU waste

Replace IEnumerable<T> fields with List<T> to avoid deeply nested
ConcatIterator chains from repeated .Concat() calls. Each WithAdditionalTrigger
call now uses List.Add() instead. Also cache the service provider hash code,
use O(1) List.Count property instead of LINQ .Count(), and add count-based
fast paths in ShouldUseSameServiceProvider to short-circuit before SequenceEqual.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@anthony-keller
Copy link
Author

For some further context, we're currently trying to diagnose threadpool exhaustion issues in our production system. We've configured dotnet monitor to produce a CPU trace and a memory dump when the pending thread count is greater than 100.

image

If you have any other questions let me know.

@anthony-keller
Copy link
Author

@PhenX Based on our usage of this change over the past 2 weeks, it has not caused any issues and we saw significant improvements.

@PhenX
Copy link
Member

PhenX commented Mar 21, 2026

I think I was confronted to this same issue a while ago, fixed it by rewriting the service registration in my code, but that was before I became a contributor. I'll compare both and take best parts :) thank you

@PhenX
Copy link
Member

PhenX commented Mar 21, 2026

I reopened it with your commit here : #215

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.

2 participants