LinqFasterer

June 23, 2022 Β· View on GitHub

Travis (.com) Codecov

A set of fast LINQ-like extension methods for Array[], List<T> and all other objects implementing IList<T> interface. LinqFasterer is designed to work well alongside LINQ, rather than to replace it completely. Under the hood LinqFasterer operates solely on Array objects which grants it a significant performance advantage over Enumerable-based LINQ.

Please don't think about LinqFasterer as 1:1 replacement. There are certain scenarios where the use of LINQ is still more appropriable. For a short chain of LINQ methods ended with ToArray() or ToList() - LinqFasterer is usually a way to go. In other cases switching to LinqFasterer may not be as beneficial or even hurt your performance. It all depends on your use case. Use your common sense and benchmark your code if needed.

To learn more please visit the mastering performance section.

πŸƒβ€β™‚οΈ Sample benchmark

Windows 10 64-bit, AMD Ryzen 7 2700X, .NET 5.0, Random int[]

MethodNMeanErrorStdDevRatioRatioSD
MinLinq10003,963.199 ns18.7389 ns11.1512 ns1.000.00
MinFaster10002,253.335 ns13.7441 ns9.0909 ns0.570.00
OrderByLinq100091,037.91 ns546.676 ns361.592 ns1.00
OrderByFaster10009,902.36 ns44.333 ns29.324 ns0.11
SumLinq10004,188.486 ns27.6469 ns18.2867 ns1.00
SumFaster1000521.019 ns5.4260 ns3.5890 ns0.12
WhereLinq10003,126.54 ns40.825 ns27.003 ns1.000.00
WhereFaster10002,199.10 ns18.613 ns11.076 ns0.700.01

A full list of benchmarks can be found in the benchmark master file.



⬆️ Features

🌀️ Installation

Install-Package LinqFasterer

Install with dotnet

dotnet add PROJECT package LinqFasterer

Install manually

Browse latest GitHub release

βš“ Requirements

This library is currently compiled in two frameworks, .NET Standard 2.0 and .NET 5.0. When installing from a package manager a more appropriate version will be used. The minimum .NET requirements are then as follows:

  • .NET 5.0
  • .NET Core 2.0
  • .NET Framework 4.6.1
  • Mono 5.4
  • Xamarin.iOS 10.14
  • Xamarin.Mac 3.8
  • Xamarin.Android 8.0
  • UWP 10.0.16299
  • Unity 2018.1

Check out this interactive .NET Standard compatibility table.

🏁 Getting started

It is stupid-simple to get started with LinqFasterer. All the methods have a very similar if not the same structure as their LINQ-equivalents. In most cases it is enough to simply append an F character to the method's name.

Examples

  • change Min(...) to MinF(...)
  • change OrderBy(...) to OrderByF(...)
  • change Take(...) to TakeF(...)
// use of LINQ, ~4200ns
var result1 = arrayWith1000Elements.Sum();

// use of LinqFasterer, ~520ns - 88% faster
var result2 = arrayWith1000Elements.SumF();

⚠️ Warning

Some methods will silently use an input sequence as the operating buffer by default to reduce memory allocations and improve performance. As an example let's take the ReverseF() method which reverses an input sequence. There are 2 ways of forcing a new sequence to be allocated and there is no performance difference between them. You can use whichever version you prefer to.

// 1. by prepending .ToArrayF(true) method
var newArray1 = inputArray.ToArrayF(true).ReverseF();

// 2. by passing forceClone: true argument
var newArray2 = inputArray.ReverseF(forceClone: true);

A full list of methods where the forceClone argument is applicable:

  • ClearF
  • ConcatF
  • DistinctF
  • ExceptF
  • FillF
  • IntersectF
  • OrderByF
  • OrderByDescendingF
  • ReverseF
  • SkipF
  • SkipLastF
  • SkipWhileF
  • SortF
  • SortDescendingF
  • TakeF
  • TakeLastF
  • TakeWhileF
  • ToArrayF
  • ToListF
  • WhereF

You don't have to remember this list - that would be stupid and very unnecessary. It is enough to follow a simple logic. The forceClone argument is applicable on methods whose output is guaranteed to be of the same or smaller size than of an input sequence and the element's Type remains unchanged.

πŸ“— Mastering performance

1) Understanding LINQ

If you know Python then it is easy to think about LINQ as generators. All the operations are deferred until the very last moment and even then, only the required sequence elements are calculated. Everything beyond that is ignored saving on CPU ticks and increasing performance. As an example, let's take a look at the following LINQ code:

var result = input.Select(v => v * 15).Take(5).ToArray();

At a first glance it looks like we multiply each element of a sequence by 15 and then we keep the 5 leading elements - wrong! LINQ is smart enough to figure out that a resulting sequence will be just 5 elements long. It will multiply only the first 5 elements by 15 and ignore everything beyond. The total number of multiplications is just 5 instead of input.Length - in contrary to what logic may tell us.

2) Understanding LinqFasterer

As previously mentioned, LinqFasterer operates solely on Array objects. Knowing that, to get the most out of this library, your code should also favor a use of arrays whenever applicable. Whenever a non-array object is used as an input sequence it is internally casted onto a newly allocated array. The following 2 lines of code are equivalent performance-wise (notice how the input sequence is a list):

var result1 = inputList.SelectF(v => v * 15);
var result2 = inputList.ToArrayF().SelectF(v => v * 15);

Let's now reconsider the same code example as in the Understanding LINQ section but with LinqFasterer methods used instead:

var result = input.SelectF(v => v * 15).TakeF(5).ToArrayF();

This code will work exactly as you think it will. At first, multiply each element of a sequence by 15 and then resize the whole sequence to keep only the very first 5 elements. That's not very optimal as in oppose to LINQ - each element of an input sequence is calculated even if it is discarded in a very next step. Let's rewrite this code into more LinqFasterer-friendly form:

var result = input.TakeF(5).SelectF(v => v * 15).ToArrayF();

Now the first step is to discard all unnecessary elements, that is, all with index 5 or higher. And then, when the sequence is of size 5, multiply each element by 15. The complexity is now equal to the one in LINQ's example. And by running a short benchmark one can see that the overall performance is even greater. All it took is some basic understanding of the methods in use and the minor code modification.

MethodNMeanErrorRatio
MasteringLinq50000104.46 ns0.753 ns1.00
MasteringLinqFasterer50000143,489.55 ns5,960.480 ns1,371.13
MasteringLinqFastererRewritten5000052.83 ns1.028 ns0.51

What you should remember from all of this is that by using LinqFasterer correctly you open your code to even greater performance. By removing Enumerable calculations (LINQ) from your code you save on CPU ticks but become more susceptible to killing overall performance with just a few lines of code. You gain a greater level of control over what happens with a sequence allowing you to master performance. However, you also become fully responsible for making sure your code is well-optimized without any bottlenecks.

πŸ—ΊοΈ Roadmap

Release 2.2

  • Parallel support

Release 2.3

  • SIMD support

Release 2.4

  • SIMD+Parallel support

πŸ“§ Contact

πŸ“ƒ License