Skip to content

[API Proposal]: Improve the performance of enumerating over interface types. #62266

@geeknoid

Description

@geeknoid

Background and motivation

Interface-based programming is encouraged as a best practice. Unfortunately, enumerating collections that are exposed through interface types triggers memory allocations and involves 2 interface calls per item iterated. In other words, enumerating an interface is slow:

List<int> l0 = new List<int>();
IList<int> l1 = new List<int>();

foreach (var x in l0)
{
    // speedy due to List<T>.Enumerator
}

foreach (var x in l1)
{
    // slow due to allocating an enumerator object, and making 2 interface calls
    // (IEnumerator<T>.MoveNext and IEnumerator<T>.Current) for each item
}

API Proposal

Please see https://github.com/geeknoid/AcceleratedEnumeration for a simple model that can deliver considerable perf benefits by avoiding allocations in many cases and cutting down the number of interface calls. The README file in that repo has perf numbers. The code I show provides benefits for most enumerations of IEnumerable, IList, IReadOnlyList, IDictionary, IReadOnlyDictionary, ISet, and IReadOnlySet.

The basic idea is to do something like:

foreach (var x in l.AcceleratedEnum())
{
}

The above works like normal enumeration, just faster. Although the model I show here requires explicit coding by the programmer to leverage, it would be conceivable to build similar tech directly into C# so that enumeration of interface types just gets faster by magic.

Note that my implementation also handles null collections, which is a nice simplification for the developer. You don't need to do an if check before you enumerate.

API Usage

foreach (var x in l.AcceleratedEnum())
{
}

Alternative Designs

As I mentioned above, you can imagine building this ugly stuff straight into the C# compiler. In fact, putting this into the compiler can likely deliver perf benefits by using byref semantics for the enumerator structs, which would avoid copying overhead. This is not possible right now since the C# compiler doesn't know how to deal with enumerators by reference.

Risks

The design intentionally avoids extra error checks, so edge cases will throw different exceptions than in the non-accelerated case. In particular, the implementation of IEnumerator.Current when there isn't anything in the collection throws different exceptions. That could be remedied at the cost of a few cycles.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions