jackmott / LinqFaster

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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

Smurf-IV opened this issue · comments

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;
    }

Just in case the VM is causing problems, this is the host, and it still shows a difference
Ideally it should be in the same region as the ArrayFirstF result.

image

try making a local copy of the array inside the function calling FirstF

try making a local copy of the array inside the function calling FirstF

I do not understand, In the Benchmark, or in the Implementation of FirstF for Span<T>?

[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);
}

in the benchmark

in the benchmark

I've shown the code, isn't is generating a local for each of the benchmarks ?

public double IntSpanFirstFast()
{
    var localArray = intArray;
    return localArray.AsSpan().FirstF(firstInts);
}

No difference ...
image

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

        return 0;
    }

    [Benchmark]
    public double IntSpanFirstFast()
    {
        int[] localArray = intArray;
        Span<int> asSpan = localArray.AsSpan();
        return asSpan.FirstF(firstInts);
    }

These are such small differences (nanoseconds) it might just be the function call overhead of calling into FirstF. Like the difference is pretty close to the latency of a single memory location lookup

Because the function is not the same for each run (private static readonly Func<int, bool> firstInts = (x) => x > 0;) then any difference seen should be taken into account.
Converting to a Span is quick and does not have a great impact (As seen by the other benchmarks), then these differences should be raising a red flag, especially as the Array.FirstF is so much quicker.

Span is quick, see the difference for Sum, i.e. Span.SumFor is slower than the Span.SumFastF

IntArraySumLinq 1000000 5,610,364.063 ns 110,617.3880 ns 178,626.4625 ns 5,569,376.563 ns - - - -
IntArraySumFast 1000000 632,074.231 ns 11,899.6764 ns 11,687.0733 ns 632,253.467 ns - - - -
IntSpanSumFor 1000000 959,628.695 ns 18,512.4370 ns 24,071.3973 ns 957,722.656 ns - - - -
IntSpanSumFast 1000000 944,971.122 ns 18,046.1893 ns 15,997.4767 ns 941,054.688 ns - - - -

Checking back through the Benchmarks...
It appears that the Span functions are slower apart from the Sum...

IntArrayAggregateLinq 1000000 6,644,730.651 ns 72,946.5557 ns 68,234.2534 ns 6,645,266.016 ns - - - -
IntArrayAggregateFast 1000000 2,155,960.964 ns 35,396.5436 ns 33,109.9488 ns 2,143,233.203 ns - - - -
IntReadOnlyArrayAggregateLinq 1000000 7,335,447.578 ns 74,533.0185 ns 69,718.2318 ns 7,309,037.891 ns - - - 128 B
IntReadOnlyArrayAggregateFast 1000000 6,347,838.229 ns 64,908.7109 ns 60,715.6484 ns 6,331,896.875 ns - - - -
IntSpanAggregateForEach 1000000 2,757,356.406 ns 39,808.8742 ns 37,237.2456 ns 2,745,133.203 ns - - - -
IntSpanAggregateFast 1000000 3,085,561.250 ns 38,936.5386 ns 36,421.2624 ns 3,079,746.094 ns - - - -
 

 

are you testing with .net core? span is slow when not on .net core

Picture states .Net 4.7.2, so nope.

yeah so span being slow is to be expected, nothing to be done about it.

yeah so span being slow is to be expected, nothing to be done about it.

So would it be prudent to move the Linq for Span<T> into an extensions dll, with a caveat that "Some" API's are not faster ?