๐ Benchmarks
GenDI includes a dedicated BenchmarkDotNet project to validate startup registration performance across four distinct strategies, giving developers the data to make an informed choice.
๐ฏ Scenariosโ
| # | Description | How registration happens | How activation happens |
|---|---|---|---|
| 1 | Manual (no GenDI) | โ๏ธ Hand-written AddSingleton<> / AddTransient<> | Container expression-tree compilation (one-time reflection) |
| 2 | GenDI โ constructor injection | โก AddGenDIServices() (compile-time generated) | Generated factory: new Service(sp.Get<A>(), sp.Get<B>()) |
| 3 | GenDI โ property injection | โก AddGenDIServices() (compile-time generated) | Generated factory: new Service { A = sp.Get<A>(), B = sp.Get<B>() } |
| 4 | Reflection scanner (worst case) | ๐ข Assembly.GetTypes() scan at startup | Container expression-tree compilation |
๐งช Benchmark projectโ
- ๐
tests/GenDI.Benchmarks - ๐
StartupRegistrationBenchmarks
Run locally:
dotnet run -c Release --project tests/GenDI.Benchmarks/GenDI.Benchmarks.csproj -- --job Short --filter "*"
โก Latest result snapshotโ
| Method | Mean | Allocated |
|---|---|---|
| โ๏ธ Manual registration (no GenDI) | 1.842 ฮผs | 5.21 KB |
| โก GenDI: constructor injection (generated) | 2.007 ฮผs | 5.68 KB |
| ๐ GenDI: property injection (generated) | 2.031 ฮผs | 5.71 KB |
| ๐ข Reflection registration (no GenDI, assembly scan) | 37.901 ฮผs | 14.54 KB |
๐ What the numbers meanโ
โ๏ธ Manual vs โก GenDI generatedโ
The manual baseline registers the same full service set as AddGenDIServices() for an
apples-to-apples comparison. Manual registration is marginally faster (~8 %) because it inlines
the registration calls directly, while GenDI bundles them inside a generated extension method.
This is a constant, one-time startup cost โ it has no effect on per-request resolution speed.
The ergonomic price of "manual" is every new service needing its own AddScoped<>() call in a
startup file. GenDI eliminates that maintenance entirely.
๐ Constructor injection vs property injection โ it's a tieโ
The performance difference between GenDI constructor injection and GenDI property injection is ยฑ1โ2 % โ within measurement noise. Both generate an explicit compiled factory lambda; the JIT produces nearly identical machine code.
๐ก Choose property injection for cleaner code. You pay no measurable performance price.
๐จ Reflection scanner โ the real cost to avoidโ
Assembly scanning at startup is ~19ร slower and allocates ~2.5ร more memory than any
GenDI-generated strategy. GenDI moves all of that scanning to compile time โ the runtime never
touches a GetTypes() call.
๐ Summaryโ
| Comparison | ๐ Winner | Margin | Takeaway |
|---|---|---|---|
| โ๏ธ Manual vs โก GenDI generated | Manual (barely) | ~8 % | Negligible; GenDI saves hours of maintenance |
| โก Constructor vs ๐ property injection | Tie | ยฑ1โ2 % (noise) | Use property injection โ zero cost, big ergonomic win |
| โก GenDI generated vs ๐ข reflection scanner | GenDI | ~19ร faster | Reflection scanning is not viable for cold-start-sensitive apps |
๐ฆ Binary size comparisonโ
These measurements use a representative minimal .NET 10 console application with three singleton services and one transient service.
๐ฅ๏ธ Environment: .NET SDK 10.0.201, linux-x64
Resultsโ
| Configuration | โ๏ธ Manual (no GenDI) | โก GenDI (ctor or property) | ๐ข Reflection scanner |
|---|---|---|---|
| Framework-dependent (folder) | 264 KB | 292 KB | 264 KB |
| Framework-dependent (app DLL) | 8 KB | 8 KB | 8 KB |
| Self-contained (folder) | ~80 MB | ~80 MB | ~80 MB |
| Trimmed self-contained (folder) | ~23 MB | ~23 MB | ~23 MB โ ๏ธ |
| NativeAOT (native binary) | 2.2 MB | 2.2 MB | 2.2 MB โ ๏ธ |
โ ๏ธ = binary is produced but crashes at runtime.
๐ What this meansโ
- โ Framework-dependent: GenDI adds 28 KB (the library DLL + PDB + XML docs). Irrelevant for any real deployment. Reflection scanner has no overhead here.
- โ Self-contained: The .NET runtime bundle (~80 MB) eclipses everything. All three strategies produce identical output sizes.
- โ
Trimmed: The IL linker statically analyses the generated factories (no reflection โ full
visibility) and removes all unused GenDI internals. Final size is identical to manual.
โ The reflection scanner triggers IL2026 / IL2072 trimmer warnings โ the implementation types get stripped and the binary crashes at startup. - โ
NativeAOT: GenDI generates zero-reflection factory code. The AOT compiler produces an
identical 2.2 MB native binary to hand-written registration.
โ The reflection scanner generates the same compiler warnings and the native binary crashes at startup โAssembly.GetTypes()is incompatible with AOT.