dotnet / dotnet/runtime

Computed ReadOnlySpan<T> properties always allocate RuntimeFieldInfoStubs in Debug Mode

Open
#122,336 5 comments 3 reactions 0 assignees View on GitHub
area-CodeGen-coreclr tenet-performance
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

I have come to notice that computed properties that return a hardcoded static buffer as a `ReadOnlySpan` always allocate a `RuntimeFieldInfoStub` on every access when compiling in debug mode.

```csharp
using System;
using System.Runtime.CompilerServices;

internal class Program
{
public static ReadOnlySpan Foo => [ 1, 2, 3, 4 ];

public static void Main(string[] args)
{
// Dummy code that does *something* with Foo.
var random = new Random();
for (int i = 0; i < 1000000; i++)
DoSomething(Foo[random.Next(Foo.Length)]);
}

private static void DoSomething(uint u)
{
// ...
}
}
```

After chatting with some of the folks over at the C# discord, it is my understanding that this happens because `Foo.get` gets compiled to a `ldtoken` plus a `call` to `System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan`. This performs a resolution of a field handle to the underlying field storing the data, as well as a bunch of other checks (see also [SharpLab](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBLANmgExAGoAfAAQCYBGAWAChyAGAAnOoDoAlAVwDsM2ALYwOAYQhCADnhhQAynIBu2MDADOAbgYNsAuXwCGuNpTbUA7AwDeDFvbYBmc0jYoWAWUN6AFOyYA2gC6LIZQAObqAJR2Drb0DoksSmEsUIZ8BJIsALwsfDAA7ixcGVlCPlHaCUn2AGbQLD56GCzYuSxMmm0sADws1ExDw93YxMQxNbWJACIQ8pIwGAAWeuE+AGIQEAHpmZIcAHIwCBib2xwAMjB84StRQVWx9gC+OlMsAR5LyxAEAJLSXA+b4rP6AqS4ADyUkEED46iOEH+fFwejWD2eLCkUGwKQwMBcbhYcwWIhWax8PBaLB4k0S8USb3oWPIznYri4MEMBChqIAnvIpBletSBAA+FhbCC5SUBahoFiURWORUoILVF5AA=)). In Release mode, the JIT correctly optimizes away this call, but in Debug mode the allocation remains.

While I understand that, generally speaking, Debug builds are not meant to be optimized for performance nor are they meant to be profiled, I still feel this unnecessarily slows down a Debug build, especially if we consider these properties are really only there to expose some static read-only data. Right now, if I want to test my user application that uses a `ReadOnlySpan` property a lot (e.g., because it uses a lot of compile-time generated static data), your only options are to use a very slow Debug build, use a Release build that is unpredictable under a debugger, or rewrite the code that minimizes the direct accesses to the property (see considered alternatives below).

All options are not really great DX in my opinion. I was therefore wondering whether a specific case could be made to also optimize `ReadOnlySpan` properties with a static back buffer in Debug mode as well.

### Configuration

Tested on fresh installations of Ubuntu 25.04 x64, Windows 10 x64 and NixOS 25.11 x64, all with a fresh .NET 10 installation as well.

### Data

Running the code above in dotMemory reveals this data:

```
Allocated type : System.RuntimeFieldInfoStub
Objects : n/a
Bytes : 144000000

Allocated by
100% FromPtr • 137.33 MB / 137.33 MB • System.RuntimeFieldInfoStub.FromPtr(IntPtr)
100% get_Foo • 137.33 MB / - • global::Program.get_Foo()
100% Main • 137.33 MB / - • global::Program.Main(String[])
► 100% [AllThreadsRoot] • 137.33 MB / - • [AllThreadsRoot]
```

### Considered Alternatives

- I have considered replacing the `ReadOnlySpan` property with a `static readonly T[]` field specifically for debug builds
```csharp
public static readonly uint[] Foo = [ 1, 2, 3, 4 ];
```
but this of course loses the benefits of `ReadOnlySpan`.

- Alternatively, the `ReadOnlySpan` could be stored in a temp variable first;
```csharp
var foo = Foo;
for (int i = 0; i < 1000000; i++)
DoSomething(foo[random.Next(foo.Length)]);
```
but this loses some of the ergonomics on how you can use the property.

### Context

For context, this came up while trying to address some issues in AvaloniaUI/Avalonia#20175 where a debug build was significantly slower when rendering text (Avalonia uses many compile-time generated static buffers to do unicode-specific text formatting and rendering).

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.