Giter Site home page Giter Site logo

jackmott / linqfaster Goto Github PK

View Code? Open in Web Editor NEW
750.0 35.0 35.0 487 KB

Linq-like extension functions for Arrays, Span<T>, and List<T> that are faster and allocate less.

License: MIT License

C# 100.00%
simd linq perfromance gamedev-library allocation csharp-library

linqfaster's Introduction

Build Status

LinqFaster

Logo

A set of Linq-Like extension methods for arrays[], Span<T>, and List<T> that are faster and allocate less. LinqFaster is designed to work well alongside Linq, rather than replace it completely. Extension method names mirror Linq names with an extra character, e.g. SelectF. If you have code that is often doing small Linq operations and immediatelly calling ToArray() or ToList(), or you are often using Min,Max,Sum,Average,or Aggregate, LinqFaster may provide massive cpu and memory usage reductions. See the documentation section below for more details.

LinqFaster now includes all relevant Linq extension methods, and many SIMD and Parallel enhanced extension methods are available as well in separate projects and nuget packages. The projects have no dependencies on each other so you can pick and choose at will.

The base LinqFaster project is now compatible with Unity3D

Sample Benchmarks

64bit Win 10, 2 Core I7 Mobile

Method TEST_SIZE Mean Allocated
OrderByLinq 100000 24,436.0135 us 1.2 MB
OrderByFast 100000 5,612.7712 us 802.1 kB
MinLinq 100000 548.9181 us 48 B
MinFast 100000 69.2122 us 0 B
MinFastSIMD 100000 14.5291 us 0 B
SumLinq 100000 541.3823 us 48 B
SumFast 100000 53.8166 us 0 B
SumFastSIMD 100000 9.7636 us 0 B
SumFastSIMDParallel 100000 3.7074 us 1.11 kB

More detailed info and benchmarks available in the benchmarks file which will be continually updated.

Features

  • SIMD enhanced array operations
  • ⬇️ Execution time
  • ⬇️ GC churn
  • ⬆️ Battery life
  • ⬇️ Sever farm costs
  • ⬇️ Co2 output
  • ⬇️ Electricity bill
  • ⬆️ L1 cache hits
  • ⬆️ FPS!

Documentation

As well, all functions are properly documented so as to be explorable via intellisense.

LinqFaster

	using JM.LinqFaster;
	
	
	//Create an array of ints with values -500 to 500
	var myArray = LinqFaster.RangeArrayF(-500, 1000);
	//Create a List<T> with 1000 elements all set to 5.0
	var myList = LinqFaster.RepeatListF(5.0, 1000);

	//Compute sum, average, max,min
	var sum = myArray.SumF();
	var average = myArray.AverageF();
	var min = myArray.MinF();
	var max = myArray.MaxF();
	
	// Compute the sum of a slice of your array using Span<T>
	// LinqFaster includes a handy extension method to make slices easier
	var sliceSum = myArray.Slice(10,20).SumF();

	//As above but on a transformation
	var sum2 = myArray.SumF(x => x*x);
	var average2 = myArray.AverageF(x => x*x);
	var min2 = myArray.MinF(x => x*x);
	var max2 = myArray.MaxF(x => x*x);

	//Do a where and a select or select and where in a single pass
	var newArray = myArray.WhereSelectF(x => x % 2 == 0,x=>x*x);
	var newArray2 = myArray.SelectWhereF(x => x * x,x => x % 2 == 0);

	//Compute the sum of only the even values in a single pass
	var filteredSum = myArray.WhereAggregateF(x => x % 2 == 0, (acc, x) => acc + x);

	//New in-place methods are provided where appropriate
	myArray.SelectInPlaceF(x => x * x);
	myArray.ReverseInPlaceF();

LinqFaster.SIMD

See MSDN documentation on System.Numerics.Vectors for more detail on SIMD in C#.

using JM.LinqFaster.SIMD;

// SIMD extension methods are only available for arrays
// SIMD functions all have an S at the end, for clarity
// and to avoid naming collisions.

var myArray = LinqFaster.RangeS(-500, 500);

// Some operations work identically to their scalar
// counterparts,but faster, as long as the machine has 
// SIMD hardware.

var sum = myArray.SumS();
bool b = myArray.ContainsS(5);
var max = myArray.MaxS();

// Floating point math is not commutative, so the results
// of operations such as Sum and Average may differ slightly.
// Accuracy is usually better.

Console.WriteLine(myFloats.SumS() == myFloats.SumF()); // --> False!

// When using selectors, you must provide one that operates on
// Vector<T>. Optionally, you may provide a second selector
// that operates on T to handle elements that are left over, 
// when your array is not evenly divisible by the Vector width.

// When there will be no leftovers
var sum = myArray.SumS( x => x*x );   // Vector<T> has overrides for *, + etc

// When there will be leftovers
var sum = myArray.SumS(x=>x*x, x=>x*x); 

// As with the regular LinqFaster project, SIMD includes
// in place versions when appropriate:

myArray.SelectInPlaceS( v => v + new Vector(1));

LinqFaster.Parallel

using JM.LinqFaster.Parallel;

// Parallel extension methods are all named with a P 
// at the end.

var sum = myList.SumP();

// Floating point math is not commutative, so the results
// of operations such as Sum and Average may differ slightly.
// Accuracy is usually better.

Console.WriteLine(myFloats.SumP() == myFloats.SumF()); // --> False!

// In some cases, unordered versions of a function are provided for
// better performance

myList.SelectUnorderedP(x => x*x);  

// All parallel functions have an optional batch size, which
// defines how many elements are operated on per task/thread.
// This can be tuned for performance.

myArray.Select(x=>x*x,myArray.Length/10);  //split the array into 10 ranges
myArray.Select(x=>x*x,10000); // split the array into ranges of 10,000 elements each

// Parallel functions cause some allocations, and requires care in their use, as they 
// may not be faster than scalar or SIMD versions depending on the workload and
// hardware available.

LinqFaster.SIMD.Parallel

See MSDN documentation on System.Numerics.Vectors for more detail on SIMD in C#.

using LingFaster.SIMD.Parallel

// These functions combine SIMD acceleration and multithreading.
// They are all named with an SP at the end, and are available
// only for arrays.

var sum = myArray.SumSP();

// Floating point math is not commutative, so the results
// of operations such as Sum and Average may differ slightly.
// Accuracy is usually better.

Console.WriteLine(myFloats.SumSP() == myFloats.SumF()); // --> False!

// All SIMD parallel functions have an optional batch size, which
// defines how many elements are operated on per task/thread.
// This can be tuned for performance.

myArray.AverageSP(myArray.Length/10);  //split the array into 10 ranges
myArray.SelectSP(x=>x*x,10000); // split the array into ranges of 10,000 elements each


// SIMD operations are already very fast, so combining them with 
// multiple threads will only be worthwhile for large arrays and/or
// for very expensive operations.

Limitations

These are purely imperative implementations of the same higher order functions that Linq provides, but unlike Linq they are not lazily evaluated. This means that when chaining functions together such as:

var a = data.Where(predicate).Select(transform).Aggregate(foo);
//or
var b = data.Select(selector).Sum();

Linq would not do any work until the calls to Sum() or Aggregate(), and thus iterate over the collection only once and allocate very little. LinqFaster used this way would iterate over the collection each time and allocate much more. Sometimes the net result will still be faster overall but the better approach is to use the combined LinqFaster operations such as SelectWhere, WhereSelect, and WhereAggregate. For example the expressions above would become:

var a = data.WhereAggregate(predicate,transform,foo);
// and
var b = data.Sum(selector);

This gets you the best of both worlds. The speed of memory locality and no allocations at all. In short, think about how you are transforming your data. In some cases normal Linq may be the better choice.

While most of the functions strive to provide indentical results to Linq, the OrderBy methods are not a stable sort, while in Linq they are.

linqfaster's People

Contributors

chrsteinert avatar jackmott avatar jnyrup avatar kharacternyk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

linqfaster's Issues

loop type

The extension methods in e.g. Sum.cs uses foreach for arrays and C-style loops for Lists, just as the benchmarks tells us.
Others such as Min.cs does not follow this practice.

For C-style loops a quick grep finds 101 occurrences with Lists and 122 occurrences for arrays.

[Question] Span_FirstF and List_FirstF are slower than just calling foreach -> Why?

image
image

    [Benchmark]
    public double IntSpanFirstForEach()
    {
        Span<int> asSpan = intArray.AsSpan();
        foreach (int i in asSpan)
        {
            if (firstInts(i))
            {
                return i;
            }
        }

        return 0;
    }

    [Benchmark]
    public double IntSpanFirstFast()
    {
        return intArray.AsSpan().FirstF(firstInts);
    }

    [Benchmark]
    public double IntListFirstLinq()
    {
        return intList.First(firstInts);
    }

    [Benchmark]
    public double IntListFirstFast()
    {
        return intList.FirstF(firstInts);
    }

    [Benchmark]
    public double IntListFirstFast1()
    {
        Predicate<int> predicate = new Predicate<int>(firstInts);
        int sourceCount = intList.Count;
        for (int i = 0; i < sourceCount; i++)
        {
            if (predicate(intList[i]))
            {
                return intList[i];
            }
        }

        return 0;
    }

Fold FastLinq package into this one?

Hey @jackmott,

I starred this repo a while ago when I made https://github.com/ndrwrbgs/FastLinq as it was the most popular alternative. It's possible for both libraries to exist concurrently, but their goals are so similar that I'd be interested in offering 1 solution.

If you have some time, could you look over the ReadMe.md, which explains the theory behind the optimizations I made (specifically avoiding IEnumerable information loss) [or check out the milestones for the planned work https://github.com/ndrwrbgs/FastLinq/milestones] and if you think

  • The overlap is enough to conflate packages
  • The overlap is only in the goal, but the approaches are different enough to keep separate

This will help me to know where to invest in the future :)

Note that the note at the top is related to dotnet/BenchmarkDotNet#155

Thanks!

Add support for Span<T>

We should add support for Span, which would allow the use of these functions on stackalloc arrays and other memory sources. If we targeted only .NET 2.0 we could change the function signatures of all methods taking arrays to Spans, but if we want to support .NET Framework and Core 1.0 we need to duplicate every array function, and make a Span version.

As well, performance opportunities may exist if we use stackalloc arrays when appropriate inside our implementations.

[BUG] ContainsS returns different result to Contains

void Main()
{
    var max = 999999;

    var nums = Enumerable.Range(0, max + 1).ToArray();

    Console.WriteLine(nums.ContainsS(max));
    Console.WriteLine(nums.Contains(max));
}

Outputs

False
True

Or another more simple example:

Console.WriteLine(new[]{1,2,3,4,5,6,7,8}.ContainsS(5));

It appears that .ContainsS fails for any of the last Vector<T>.Count elements when (haystack.Length % Vector<T>.Count) == 0

GroupBy missing as a replacement

Love this project. Has made some huge performance improvements on some large datasets using linq. Question, is there a reason why GroupBy is not available? or is this a method that is difficult to improve in general?

[Enhancement] Please can you add a GetMinMax() style function ?

Something better than this ?

    public static void GetMinMax(double[] values, out double min, out double max)
    {
        min = double.NaN;
        max = double.NaN;
        int length = values.Length;
        if (length > 0)
        {
            min = values[0];
            max = min;
            for (int i = 1; i < length; i++)
            {
                double value = values[i];
                if (min > value)
                {
                    min = value;
                }
                else if (max < value)
                {
                    max = value;
                }
            }
        }
    }

[Bug/Enhancement] Partitioner parameter value

[Enhancement] I'm trying to "Optimise" the use of parallel processing, so I though that if I pass in a "chunking" value of what I feel will be beneficial to be in a single thread chunk before parallel processing needs to happen (i.e. simple comparison logic for less than 100 values it quicker in a single thread than the overhead of setting up the parallel processing)
I thought the following would be ideal:
maxGradeCount += filtered.CountP(ft=> ft.Number == maxGrade, 100);
where filtered can contain 0 to some large number values above 10,000.

[BUG] What I get is Specified argument was out of the range of valid values. when filtered has 0 elements. This does not occur when I use CountF

[BUG] Different answers between SumF and SumSP

I was using SumSP and was getting an answer of 18 but on one machine (Lower spec) it consistently got 17 ???
So I had a look at the code and on the machine that get 18, and I get the following answers
(Sorry cannot give you the data set as it is embedded in the datastores)

        float lengthSPM = lengths.SumSP(MIN_STEP_FILTER_POINTS); // get 18
        float lengthSP = lengths.SumSP(); // get 13
        float lengthS = lengths.SumS(); // get 17
        float lengthF = lengths.SumF(); // get 17

[BUG] Empty Sources produce an exception, whilst Normal Linq Does not

Using your .Where### APIs on an Empty List or Array causes an exception to be thrown, whilst the Default Linq .Where does not.
This means that i have to create lot of wrapper code to deal with
i.e. check for Any() before calling your API's 'else' set to new List<T>()

This is not the same as passing null, just Empty Arrays or Lists from other calls.

The same Exception applies to your Select### API's as well.

Progress Checklist

  • Regular Linq Extension Method Coverage

  • Regular Linq Extension Method Tests

  • Parallel Linq Extension Method Coverage

  • Parallel Linq Extension Method Tests

  • SIMD Linq Extension Method Coverage

  • SIMD Linq Extension Method Tests

  • Parallel SIMD Linq Extension Method Coverage

  • Parallel SIMD Linq Extension Method Tests

[Bug] Span SumF is slower than a for loop over a span

image

    [Benchmark]
    public int IntSpanESumLinq()
    {
        int val = 0;
        foreach (int i in intArray.AsSpan())
        {
            val += i;
        }
        return val;
    }

    [Benchmark]
    public int IntSpanFSumLinq()
    {
        int val = 0;
        Span<int> span = intArray.AsSpan();
        for (int index = 0; index < span.Length; index++)
        {
            val += span[index];
        }

        return val;
    }

    [Benchmark]
    public int IntSpanSumFast()
    {
        return intArray.AsSpan().SumF();
    }

Benchmark request

Smaller List<T>, Sum and Where(x x=>x%2000).Select(x =>x*x)

BenchmarkDotNet=v0.10.3.0, OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700 CPU 3.40GHz, ProcessorCount=8
Frequency=3328121 Hz, Resolution=300.4698 ns, Timer=TSC
  [Host]    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2046.0
  RyuJitX64 : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2046.0

Job=RyuJitX64  Jit=RyuJit  Platform=X64  
Method TEST_SIZE Mean StdDev Gen 0 Allocated
SumLinq 10 85.1899 ns 0.1173 ns 0.0062 40 B
SumLinqFaster 10 10.1558 ns 0.0872 ns - 0 B
WhereSelect 10 209.9139 ns 0.3424 ns 0.0558 288 B
WhereSelectMinLinqFaster 10 93.0835 ns 0.1782 ns 0.0261 136 B
SumLinq 100 596.9848 ns 0.9774 ns - 40 B
SumLinqFaster 100 90.6256 ns 0.2687 ns - 0 B
WhereSelect 100 1,128.6667 ns 1.4259 ns 0.1450 808 B
WhereSelectMinLinqFaster 100 672.5056 ns 1.2559 ns 0.1301 656 B
SumLinq 1000 5,729.0542 ns 5.9019 ns - 40 B
SumLinqFaster 1000 823.4186 ns 1.4371 ns - 0 B
WhereSelect 1000 9,000.8336 ns 10.6227 ns 0.7039 4.46 kB
WhereSelectMinLinqFaster 1000 5,677.9769 ns 25.0852 ns 0.8392 4.31 kB

[Bug] using SumS on a ushort[] throws System.InvalidCastException

Code:

                    ushort[] pRowArrayClip = samePool.Rent(countClip);
                    Array.Copy(pRowArrayFull, pRowArrayClip, countClip);
                    double average = pRowArrayClip.SumS();

Exception:

                   System.InvalidCastException : Specified cast is not valid.
                       at JM.LinqFaster.SIMD.LinqFasterSIMD.SumS[T](T[] source)
                       at #########

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.