dotnet / dotnet/runtime

Using 'DotNetObjectAllocDiagnoser' prevents De-Abstraction optimizations

Open
#123,005 7 comments 0 reactions 1 assignee Claimed by @AndyAyersMS View on GitHub
area-CodeGen-coreclr
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

De-Abstraction in .NET 10 is awesome, but it's known to be fragile. Here is another example of its fragility:

Let's say we want to investigate the memory allocations from a benchmark by using `DotNetObjectAllocDiagnoser` (here is a blogpost that explains the usage: https://devblogs.microsoft.com/visualstudio/benchmarking-with-visual-studio-profiler/#getting-insight-into-the-benchmark):

```csharp
[MemoryDiagnoser]
[ShortRunJob]
// Using the following Diagnoser to export benchmark allocations in a profiling session.
[DotNetObjectAllocDiagnoser]
[DotNetObjectAllocJobConfiguration]
public class ForEachBenchmarkWithAllocDiagnoser
{
static List GenerateData() => Enumerable.Range(1, 1000).Select(n => n).ToList();
private IList list = GenerateData();

[Benchmark]
public int Foreach_Over_ListAsIList()
{
int result = 0;
foreach (var e in list)
{
result += e;
}

return result;
}
}
```

And here is the benchmark without the diagnoser:
```csharp
[MemoryDiagnoser]
[ShortRunJob]
public class ForEachBenchmark
{
static List GenerateData() => Enumerable.Range(1, 1000).Select(n => n).ToList();
private IList list = GenerateData();

[Benchmark]
public int Foreach_Over_ListAsIList()
{
int result = 0;
foreach (var e in list)
{
result += e;
}

return result;
}
}
```

Here are the results. The first becnhmark (with the diagnoser):

```
Job=ShortRun EnvironmentVariables=DH_CORPROFILER_START_PAUSED=1,DIAGHUB_CORPROFILER_DISABLE_OBJECTCOUNTER=1,COR_ENABLE_PROFILING=1,COR_PROFILER={B874B9B4-F0B1-4ABF-8D60-126AF315109F},COR_PROFILER_PATH_32=C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\IDE\CommonExtensions\Platform\DiagnosticsHub\x86\DiagnosticsHub.CorProfiler.dll,COR_PROFILER_PATH_64=C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\IDE\CommonExtensions\Platform\DiagnosticsHub\amd64\DiagnosticsHub.CorProfiler.dll,CORECLR_ENABLE_PROFILING=1,CORECLR_PROFILER={B874B9B4-F0B1-4ABF-8D60-126AF315109F},CORECLR_PROFILER_PATH_32=C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\IDE\CommonExtensions\Platform\DiagnosticsHub\x86\DiagnosticsHub.CorProfiler.dll,CORECLR_PROFILER_PATH_64=C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\IDE\CommonExtensions\Platform\DiagnosticsHub\amd64\DiagnosticsHub.CorProfiler.dll,CORECLR_PROFILER_PATH_ARM=C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\IDE\CommonExtensions\Platform\DiagnosticsHub\arm\DiagnosticsHub.CorProfiler.dll,CORECLR_PROFILER_PATH_ARM64=C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\IDE\CommonExtensions\Platform\DiagnosticsHub\arm64\DiagnosticsHub.CorProfiler.dll,COMPlus_ReadyToRun=0,COMPlus_ZapDisable=1,CORPROFILER_SAMPLE_RATE=1,DOTNET_RichDebugInfo=1,COMPlus_TieredCompilation=0 IterationCount=3
LaunchCount=1 WarmupCount=3

| Method | Mean | Error | StdDev | Gen0 | Allocated |
|------------------------- |---------:|---------:|----------:|-------:|----------:|
| Foreach_Over_ListAsIList | 5.483 us | 1.438 us | 0.0788 us | 0.0076 | 40 B |
```

The second results without the diagnoser:
```
| Method | Mean | Error | StdDev | Allocated |
|------------------------- |---------:|---------:|---------:|----------:|
| Foreach_Over_ListAsIList | 485.9 ns | 214.0 ns | 11.73 ns | - |
```

So we have an observer effect here: when we try to collect more infromation about the runtime behavior, the behavior itself changes. In this case it might not be a big deal, but in some other cases, when we analyze the delegate de-allocation it might be very hard to understand what are the allocations.

My guess is that some of the flags used by the diagnoser (like `COR_ENABLE_PROFILING`) are affecting the JIT's behavior.

### Reproduction Steps

See the examples above.

### Expected behavior

The optimizations should work when the diagnoser is enabled.

### Actual behavior

The JIT stops de-abstracting the code when the diagnoser is used.

### Regression?

No. This is a new functionality.

### Known Workarounds

_No response_

### Configuration

_No response_

### Other information

_No response_

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.