dotnet / dotnet/runtime

`LambdaExpression.Compile()` is >10x slower on .NET 10 for an EntityGraphQL-generated projection tree; `Compile(preferInterpretation: true)` is unaffected

Open
#127,203 5 comments 0 reactions 0 assignees View on GitHub
area-System.Linq.Expressions tenet-performance untriaged
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

## Summary

While benchmarking EntityGraphQL query compilation, I found a regression when compiling different `LambdaExpression` shapes on `.NET 10.0.6` compared to `.NET 9.0.6`.

The important part is that this is not the full GraphQL pipeline. I isolated the issue to compiling the exact `LambdaExpression` generated for a query projection - I hope.

On the same machine:

- `.NET 9.0.6`: `Compile()` = `252.950 us`
- `.NET 10.0.6`: `Compile()` = `5.143 ms`
- `.NET 9.0.6`: `Compile(preferInterpretation: true)` = `2.329 us`
- `.NET 10.0.6`: `Compile(preferInterpretation: true)` = `2.046 us`

So the slowdown appears to be in the non-interpreted `LambdaExpression.Compile()` path for this tree shape.

## Environment

- macOS Tahoe 26.4.1 (`Darwin 25.4.0`)
- Apple M1 Max
- SDK `10.0.202`
- Benchmarked runtimes:
- `.NET 9.0.6`
- `.NET 10.0.6`

## Repro

Repository benchmark: https://github.com/EntityGraphQL/EntityGraphQL

- Benchmark file: `src/Benchmarks/GraphQLLambdaCompileBenchmarks.cs`
- Run:

```bash
dotnet run -c Release --project src/Benchmarks/Benchmarks.csproj --framework net9.0 --filter '*GraphQLLambdaCompileBenchmarks*'
dotnet run -c Release --project src/Benchmarks/Benchmarks.csproj --framework net10.0 --filter '*GraphQLLambdaCompileBenchmarks*'
```

This benchmark:

1. Parses a small GraphQL query.
2. Builds the exact projection `LambdaExpression` EntityGraphQL would execute.
3. Benchmarks only:
- `lambda.Compile()`
- `lambda.Compile(preferInterpretation: true)`

## Benchmark Output

`.NET 9.0.6`

```text
| Method | Mean | Allocated |
|--------------------------- |-----------:|----------:|
| Compile | 252.950 us | 11.87 KB |
| CompilePreferInterpretation| 2.329 us | 3.45 KB |
```

`.NET 10.0.6`

```text
| Method | Mean |
|--------------------------- |---------:|
| Compile | 5.143 ms |
| CompilePreferInterpretation| 2.046 us |
```

## Notes

- A simpler synthetic benchmark in `src/Benchmarks/ExpressionCompileBenchmarks.cs` does **not** reproduce the regression.
- The regression seems sensitive to the exact tree shape generated by EntityGraphQL rather than expression compilation in general.
- In EntityGraphQL, using `Compile(preferInterpretation: true)` for one-shot compile-and-run paths is an effective mitigation, but it would still be useful to understand what changed in the IL-emitting `Compile()` path between .NET 9 and .NET 10.

## Question

Was there an intentional change in the non-interpreted `System.Linq.Expressions` compile pipeline in .NET 10 that would explain this tree-shape-specific slowdown, or does this look like a runtime regression worth investigating further?

Or have I missed something?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.