Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LINQ Api support for static lambda's #45197

Closed
sonnemaf opened this issue Nov 25, 2020 · 6 comments
Closed

LINQ Api support for static lambda's #45197

sonnemaf opened this issue Nov 25, 2020 · 6 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Linq untriaged New issue has not been triaged by the area owner

Comments

@sonnemaf
Copy link
Contributor

sonnemaf commented Nov 25, 2020

Background and Motivation

C# 9 has support for static lambdas. They can avoid memory allocations. Wouldn't it be nice if the LINQ extension methods got an extra overload which support these.

Proposed API

In the example below I have created an extra overload for the FirstOrDefault() method. This is just an example, it should be optimized similar to the current implementation. The same solution (overloads) should also be added to all other methods like, Where(), Last(), Count(), etc.

public static TSource FirstOrDefault<TSource, TState>(this IEnumerable<TSource> source, Func<TSource, TState, bool> predicate, TState state) {
    foreach (var item in source) {
        if (predicate(item, state)) return item;
    }
    return default;
}

-->

Usage Examples

The call of this overloaded FirstOrDefault() method would not allocate anything.

var l = new List<Employee> {
    new Employee("Fons", 2000),
    new Employee("Jim", 4000),
    new Employee("Ellen", 3000),
};

var limit = 3500;

var emp = l.FirstOrDefault(static (emp, limit) => emp.Salary > limit, limit);

Console.WriteLine(emp);

The call of this current/normal FirstOrDefault() method will allocate 64 bytes (size of the delegate).

var l = new List<Employee> {
    new Employee("Fons", 2000),
    new Employee("Jim", 4000),
    new Employee("Ellen", 3000),
};

var limit = 3500;

var emp = l.FirstOrDefault(emp => emp.Salary > limit);

Console.WriteLine(emp);

A more complex example is the use of a ValueTuple for if you want to use multiple 'states'.

static void Main(string[] args) {

    var l = new List<Employee> {
        new Employee("Fons", 2000),
        new Employee("Jim", 4000),
        new Employee("Ellen", 3000),
    };

    var min = 2500;
    var max = 3500;

    var emp = l.FirstOrDefault(static (emp, state) => emp.Salary > state.min && emp.Salary < state.max, (min, max));

    Console.WriteLine(emp);
}

Alternative Designs

Risks

These methods should probably also be Entity Framework Core and other LINQ providers.

@sonnemaf sonnemaf added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Nov 25, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Linq untriaged New issue has not been triaged by the area owner labels Nov 25, 2020
@ghost
Copy link

ghost commented Nov 25, 2020

Tagging subscribers to this area: @eiriktsarpalis, @jeffhandley
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and Motivation

C# 9 has support for static lambdas. They can avoid memory allocations. Wouldn't it be nice if the LINQ extension methods got an extra overload which support these.

Proposed API

In the example below I have created an extra overload for the FirstOrDefault() method. This is just an example, it should be optimized similar to the current implementation. The same solution (overloads) should also be added to all other methods like, Where(), Last(), Count(), etc.

public static TSource FirstOrDefault<TSource, TState>(this IEnumerable<TSource> source, Func<TSource, TState, bool> predicate, TState state) {
    foreach (var item in source) {
        if (predicate(item, state)) return item;
    }
    return default;
}

-->

Usage Examples

The call of this overloaded FirstOrDefault() method would not allocate anything.

var l = new List<Employee> {
    new Employee("Fons", 2000),
    new Employee("Jim", 4000),
    new Employee("Ellen", 3000),
};

var limit = 3500;

var emp = l.FirstOrDefault(static (emp, limit) => emp.Salary > limit, limit);

Console.WriteLine(emp);

The call of this current/normal FirstOrDefault() method will allocate 64 bytes (size of the delegate).

var l = new List<Employee> {
    new Employee("Fons", 2000),
    new Employee("Jim", 4000),
    new Employee("Ellen", 3000),
};

var limit = 3500;

var emp = l.FirstOrDefault(emp => emp.Salary > limit);

Console.WriteLine(emp);

Alternative Designs

Risks

These methods should probably also be Entity Framework Core and other LINQ providers.

Author: sonnemaf
Assignees: -
Labels:

api-suggestion, area-System.Linq, untriaged

Milestone: -

@huoyaoyuan
Copy link
Member

You still need to pay for overhead for indirect invoke here. When talking about LINQ's performance, only reducing closure allocation is typically not enough.

@kevingosse
Copy link
Contributor

Related: #19534

@stephentoub
Copy link
Member

stephentoub commented Nov 25, 2020

C# 9 has support for static lambdas. They can avoid memory allocations. Wouldn't it be nice if the LINQ extension methods got an extra overload which support these.

To clarify, the ability to add static to a lambda hasn't changed the allocation profile for lambdas: all it does is tell the compiler not to allow anything to be closed over, giving you an error if you try. Since the introduction of lambdas, the delegate created for a lambda would be cached if it doesn't close over anything; static doesn't impact the code generation.

@eiriktsarpalis
Copy link
Member

LINQ prioritizes convenience over performance, so I don't see much point in effectively doubling our API surface in order to avoid a few allocations.

I'm also concerned about the ergonomics of applying this optimization in chained calls. It makes code look more convoluted and increases the probability of introducing bugs.

Finally, it is not evident that the proposed alternative is necessarily faster. People could use heap allocated state objects (negating the point of the optimization) or large structs (which would need to be copied on each invocation). But even if you're careful, the state passing comes at a cost: consider this benchmark.

@eiriktsarpalis
Copy link
Member

I'm going to close this issue. Feel free to reopen if you would like to continue contributing to this conversation.

@ghost ghost locked as resolved and limited conversation to collaborators Jan 1, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Linq untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

6 participants