ZLinq icon indicating copy to clipboard operation
ZLinq copied to clipboard

[Feature Request] overloads that take a context to avoid lambda capture allocations

Open lgarczyn opened this issue 6 months ago • 2 comments

A pretty common source of allocations for us is to use something like

var owner = GetOwner();
var best = entries
  .Where(e => e.Owner == owner).
  .OrderBy(e => owner.GetScore(e))
  .FirstOrDefault();

Unfortunately, both examples capture the local scope and allocate every time. One struct linq library I saw was using a context parameter like so:

var owner = GetOwner();
var best = entries
  .Where(owner, static (e, context) => e.Owner == context).
  .OrderBy(owner, static (e, context) => context.GetScore(e))
  .FirstOrDefault();

This is less readable, but gives equivalent performance and allocations as a fully unwrapped version.

Is there any chance of some way to solve this problem being implemented?

lgarczyn avatar Jun 17 '25 13:06 lgarczyn

There are no plans to add this. I know that such overloads are effective ( Cysharp/R3 adds them to key methods like Where and Select). However, in ZLinq, this would increase the number of types, and there could be a combinatorial explosion with methods that have individual optimizations for subsequent types, making it somewhat difficult to add such overloads.

neuecc avatar Jun 18 '25 00:06 neuecc

It is better to make your own ZipRepeat but you can write this way. (I know this workaround is not good for OrderBy)

var best = entries.Zip(ValueEnumerable.Repeat(owner,int.MaxValue))
    .Where(static pair => pair.First.Owner == pair.Second)
    .OrderBy(static pair =>  pair.Second.GetScore(pair.First))
    .FirstOrDefault().First;

akeit0 avatar Jun 18 '25 00:06 akeit0

@lgarczyn FWIW .NET 10 teaches the compiler to stack-allocate the closures if it can be proven that the delegate does not escape. Once the release ships, perhaps, it can be investigated which methods in ZLinq need to be aggressively inlined to facilitate this (with careful consideration on the impact it creates on inlining budget, it was increased in .NET 10 but is still a limitation).

neon-sunset avatar Jul 07 '25 17:07 neon-sunset

@lgarczyn FWIW .NET 10 teaches the compiler to stack-allocate the closures if it can be proven that the delegate does not escape. Once the release ships, perhaps, it can be investigated which methods in ZLinq need to be aggressively inlined to facilitate this (with careful consideration on the impact it creates on inlining budget, it was increased in .NET 10 but is still a limitation).

I understand, though in my case I don't think unity will upgrade to .NET 10 in the next decade ^^

lgarczyn avatar Jul 07 '25 18:07 lgarczyn