ILLink Roslyn analyzer: analysis time is quadratic in the number of lambdas in one method
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 48/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- csharp
- Lĩnh vực
- performance
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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.
- Ngôn ngữ chính
- C#
- Star
- 18.4k
- Fork
- 5.8k
- Merge trung bình
- 2 ngày 19 giờ
- Pull request đã merge (30 ngày)
- 590
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của dotnet/runtime
-
area-Setup untriaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
dotnet/runtime#137376 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
area-System.Memory untriaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
dotnet/runtime#137334 · 4 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ImmutableSortedSet<T>.Builder: enumerating through IEnumerable<T> doesn't throw on modification and can return wrong itemsCó thể đã có người làm @aw0lid đã nhận 3 ngày trước. Đang mởarea-System.Collections
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
dotnet/runtime#137167 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
DbTransaction.SupportsSavepoints docs say Release/ReleaseAsync are expected to throw, but they are no-ops by designCó thể đã có người làm @alaricd đã nhận 7 ngày trước. Đang mởarea-System.Transactions untriaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
dotnet/runtime#137040 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
FileSystemGlobbing: Matcher throws ArgumentOutOfRangeException when the rootDir is the same value as the fileCó thể đã có người làm @makazeu đã nhận 7 ngày trước. Đang mởarea-Extensions-FileSystem
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
dotnet/runtime#136989 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của dotnet/runtime
Issue tương tự
-
bug emitter:client:csharp
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
microsoft/typespec#12572 · 1 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
needs-area-label
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 2 ngày
-
AWS Lambda: KeyNotFoundException drops HTTP-triggered transactions when allowAllHeaders is disabledĐang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
newrelic/newrelic-dotnet-agent#3949 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
area-networking
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
dotnet/aspnetcore#70545 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 87/100
Maintainer thường phản hồi trong vòng 1 ngày