LinqFaster icon indicating copy to clipboard operation
LinqFaster copied to clipboard

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

Open Smurf-IV opened this issue 5 years ago • 15 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;
    }

Smurf-IV avatar Aug 02 '19 11:08 Smurf-IV

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

Smurf-IV avatar Aug 02 '19 12:08 Smurf-IV

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

jackmott avatar Aug 02 '19 13:08 jackmott

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

Smurf-IV avatar Aug 02 '19 13:08 Smurf-IV

in the benchmark

jackmott avatar Aug 02 '19 14:08 jackmott

in the benchmark

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

Smurf-IV avatar Aug 02 '19 14:08 Smurf-IV

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

jackmott avatar Aug 02 '19 14:08 jackmott

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

Smurf-IV avatar Aug 02 '19 14:08 Smurf-IV

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

jackmott avatar Aug 02 '19 14:08 jackmott

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

Smurf-IV avatar Aug 02 '19 14:08 Smurf-IV

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

 

Smurf-IV avatar Aug 02 '19 14:08 Smurf-IV

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

jackmott avatar Aug 02 '19 14:08 jackmott

Picture states .Net 4.7.2, so nope.

Smurf-IV avatar Aug 02 '19 14:08 Smurf-IV

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

jackmott avatar Aug 02 '19 15:08 jackmott

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 ?

Smurf-IV avatar Aug 03 '19 07:08 Smurf-IV

#21

Smurf-IV avatar Aug 08 '19 09:08 Smurf-IV