ILLink Roslyn analyzer: analysis time is quadratic in the number of lambdas in one method
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 48/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- csharp
- Ambito
- performance
Direzione di ricerca
Start in ILLink.RoslynAnalyzer at LocalDataFlowVisitor.VisitFlowAnonymousFunction and trace how InterproceduralState.TrackMethod feeds InterproceduralAnalyze; the report identifies repeated method-set rebuilding and fixpoint re-analysis as likely causes. Reproduce with the supplied generated lambda case, then profile the analyzer to confirm the hot path. Done when analysis time scales linearly with lambda count and the repro still produces no trim warnings.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Description
DynamicallyAccessedMembersAnalyzer (ILLink.RoslynAnalyzer) takes time quadratic in the number of lambdas inside one method. A method is one operation block, so all of it runs on a single thread and shows up directly as build wall time. Source generators that build delegate tables in a static constructor (for example compile-time DI containers) hit this with a few thousand lambdas. The same lambda bodies written as separate methods stay linear.
The analyzer runs when any of EnableTrimAnalyzer, EnableAotAnalyzer or EnableSingleFileAnalyzer is true, so IsTrimmable, IsAotCompatible, PublishAot, PublishTrimmed and PublishSingleFile all trigger it. Each of the three properties alone costs the same, and the analysis runs even when all its diagnostics are suppressed via .editorconfig or [UnconditionalSuppressMessage]. Generated files are analyzed too (ConfigureGeneratedCodeAnalysis(Analyze | ReportDiagnostics)).
Reproduction steps
Repro.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
</Project>
Generate Users.cs with L lambdas in one static constructor (PowerShell, L = 8000 here):
$entries = [string]::Join('', (1..8000 | ForEach-Object { ' static h => h,' }))
@"
public class Holder { }
public static class Users
{
public static readonly System.Func<Holder, object>[] Table;
static Users() { Table = new System.Func<Holder, object>[] {$entries }; }
}
"@ | Out-File -Encoding utf8 Users.cs
Then compare:
dotnet build --no-incremental -p:UseSharedCompilation=false -p:ReportAnalyzer=true -v:d
dotnet build --no-incremental -p:UseSharedCompilation=false -p:ReportAnalyzer=true -v:d -p:EnableTrimAnalyzer=false -p:EnableAotAnalyzer=false -p:EnableSingleFileAnalyzer=false
The code produces no trim warnings.
Expected behavior
Analysis time grows linearly with the number of lambdas, as it does when the same bodies are separate methods.
Actual behavior
csc /reportanalyzer, clean full compile, no compiler server, median of 3 runs:
| Lambdas in one method (L) | 1,000 | 2,000 | 4,000 | 8,000 | 16,000 |
|---|---|---|---|---|---|
DynamicallyAccessedMembersAnalyzer time |
0.57 s | 1.11 s | 4.58 s | 20.1 s | 118 s |
| Total compile wall, analyzer on | 1.7 s | 2.4 s | 6.8 s | 22.8 s | 120.4 s |
| Total compile wall, same bodies as separate methods | 1.3 s | 1.6 s | 1.8 s | 2.5 s | 4.2 s |
The exponent is 2.1 to 2.6 from 2,000 lambdas up. A plain dotnet build of the repro with 6,000 lambdas takes 13.0 s with the analyzer and 2.8 s without it.
In the real-world case (a generated DI container), the analyzer took 517 s at 800 services while the generator took under a second and the generated code grew linearly with the service count.
The 11.0.0-rc.3 analyzer package behaves the same (17.3 s against 18.6 s on the real-world case at 200 services).
Regression?
Not known.
Known workarounds
-
Replace lambdas with method groups, or split the method into chunks. Both remove the wall time cost. This is what we did in the generator.
-
Turn the analyzer off for local builds and keep it on for Release and CI. All three properties are needed, because each one enables the full analysis:
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <EnableTrimAnalyzer>false</EnableTrimAnalyzer> <EnableAotAnalyzer>false</EnableAotAnalyzer> <EnableSingleFileAnalyzer>false</EnableSingleFileAnalyzer> </PropertyGroup>
Configuration
- .NET SDK 10.0.403 (Microsoft.NET.ILLink.Tasks 10.0.14),
net10.0 - Windows 11 Pro 26200, x64, Ryzen 9 6900HX (8 cores, 16 threads)
Other information
Likely cause: LocalDataFlowVisitor.VisitFlowAnonymousFunction calls InterproceduralState.TrackMethod for every lambda. TrackMethod has no "already tracked" check and rebuilds the whole method set each time (a new List, then a new ValueSet backed by a HashSet). InterproceduralAnalyze then re-analyzes every tracked method in each fixpoint pass. Checking for an already-tracked method and keeping a mutable set would make this linear.
Related: #136567. GetFlags (called from IsTypeInterestingForDataflow) walks the base-type chain and runs IsTypeOf on every direct interface on every call, with no per-type cache. In the same generated code, a type implements about two interfaces per registered service, and about 35 members per service handle values of that type, so the total analyzer CPU stays quadratic even after the lambdas are gone. Standalone: 2,000 one-line methods whose parameter type implements K = 0 / 200 / 400 / 800 interfaces cost 0.92 / 2.34 / 3.66 / 6.39 s of analyzer time, about 2.7 µs per method per interface. The two effects multiply when lambdas take or capture such a type.
Disclosure: I used AI to investigate the slow-down and draft this issue.
- Lingua principale
- C#
- Stelle
- 18.4k
- Fork
- 5.8k
- Merge medio
- 2g 19h
- PR unite (30g)
- 590
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di dotnet/runtime
-
area-Setup untriaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
dotnet/runtime#137376 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
area-System.Memory untriaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
dotnet/runtime#137334 · 4 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
ImmutableSortedSet<T>.Builder: enumerating through IEnumerable<T> doesn't throw on modification and can return wrong itemsForse già presa @aw0lid l’ha presa 3 giorni fa. Apertaarea-System.Collections
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
dotnet/runtime#137167 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
DbTransaction.SupportsSavepoints docs say Release/ReleaseAsync are expected to throw, but they are no-ops by designForse già presa @alaricd l’ha presa 7 giorni fa. Apertaarea-System.Transactions untriaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
dotnet/runtime#137040 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
FileSystemGlobbing: Matcher throws ArgumentOutOfRangeException when the rootDir is the same value as the fileForse già presa @makazeu l’ha presa 7 giorni fa. Apertaarea-Extensions-FileSystem
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
dotnet/runtime#136989 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di dotnet/runtime
Issue simili
-
bug emitter:client:csharp
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
microsoft/typespec#12572 · 1 commento · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
needs-area-label
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 2 giorni
-
AWS Lambda: KeyNotFoundException drops HTTP-triggered transactions when allowAllHeaders is disabledApertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
newrelic/newrelic-dotnet-agent#3949 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
area-networking
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
dotnet/aspnetcore#70545 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 87/100
I maintainer di solito rispondono entro 1 giorno