Skip to content

JIT de-abstraction for generated ReadOnlyArray works for static not for instance fields #122856

@SergeyTeplyakov

Description

@SergeyTeplyakov

Description

Here is a benchmark that shows the behavior

[MemoryDiagnoser]
//[ShortRunJob(RuntimeMoniker.Net90)]
[ShortRunJob(RuntimeMoniker.Net10_0)]
[CategoriesColumn]
[HideColumns(Column.Job, Column.Error, Column.Median, Column.RatioSD, Column.Ratio, Column.AllocRatio, Column.StdDev, Column.Gen0)]
public class ForeachOverCollectionLiterals
{
    // For IList the optimization works in both cases, since the actual type is List<T>
    private readonly IList<int> List = [1, 2, 3, 4, 5];
    // Iterating over a readonly interface will allocate for instance fields
    // and won't allocate for static fields!
    // It seems that the JIT compiler can't optimized a case when GetEnumerator delegates
    // the work to a field.
    // See GeneratedReadOnlyArray implementation.
    private readonly IEnumerable<int> ReadOnlyList = [1, 2, 3, 4, 5];
    private static readonly IEnumerable<int> StaticReadOnlyList = [1, 2, 3, 4, 5];

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

        return result;
    }
    
    [Benchmark]
    public int Foreach_Over_IReadOnlyList()
    {
        int result = 0;
        foreach (var e in ReadOnlyList)
        {
            result += e;
        }

        return result;
    }
    
    [Benchmark]
    public int Foreach_Over_IReadOnlyList_Static()
    {
        int result = 0;
        foreach (var e in StaticReadOnlyList)
        {
            result += e;
        }

        return result;
    }
}

The output:

| Method                            | Mean      | Allocated |
|---------------------------------- |----------:|----------:|
| Foreach_Over_IList                |  3.411 ns |         - |
| Foreach_Over_IReadOnlyList        | 11.999 ns |      32 B |
| Foreach_Over_IReadOnlyList_Static |  1.192 ns |         - |

The JIT definitely can de-vritualize calls on instance fields, if the actual type of the field is a concrete collection type, the de-virtualization works.

It seems that its something to do with how ReadOnlyArray is represented:

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return ((IEnumerable<T>)_items).GetEnumerator();
    }

Reproduction Steps

See the example above.

Expected behavior

The behavior should be the same for instance and static fields.

Actual behavior

The iterator is still allocated on the heap.

Regression?

No.

Known Workarounds

No response

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions