LinqFaster icon indicating copy to clipboard operation
LinqFaster copied to clipboard

[Enhancement] Can these API's also work on IReadOnlyList<T>

Open Smurf-IV opened this issue 5 years ago • 20 comments

Smurf-IV avatar Jul 12 '19 09:07 Smurf-IV

I don't think there would be any performance difference since IEnumerable would still be happening behind the scenes.

jackmott avatar Jul 12 '19 11:07 jackmott

IList has a this[] accessor ?

Smurf-IV avatar Jul 12 '19 14:07 Smurf-IV

If you want, set up a little microbenchmark, doing sum or max or something with an Ilist with linq for using a for loop and the accessor.

If there is a substantial difference I could think about supporting IList.

jackmott avatar Jul 12 '19 15:07 jackmott

I took this up because I was interested in the profile. Of course the results will depend on the underlying implementation, but I was curious if the single interface call to this[] could outperform the double interface calls to MoveNext() and Current.

LegacyJIT Results:

BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.765 (1803/April2018Update/Redstone4)
Intel Core i7-6820HQ CPU 2.70GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
  [Host]   : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3416.0
  ShortRun : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3416.0

Job=ShortRun  IterationCount=3  LaunchCount=1
WarmupCount=3
Method Mean Error StdDev
EnumerateEnumerable 6.574 us 2.8566 us 0.1566 us
EnumerateList 2.528 us 0.8032 us 0.0440 us
SumEnumerable 6.758 us 0.6564 us 0.0360 us
SumList 3.221 us 1.0518 us 0.0577 us

RyuJIT x64 Results:

BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.765 (1803/April2018Update/Redstone4)
Intel Core i7-6820HQ CPU 2.70GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
  [Host]   : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3416.0
  ShortRun : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3416.0

Job=ShortRun  Jit=RyuJit  Platform=X64
IterationCount=3  LaunchCount=1  WarmupCount=3
Method Mean Error StdDev
EnumerateEnumerable 9.898 us 9.115 us 0.4996 us
EnumerateList 3.695 us 2.075 us 0.1137 us
SumEnumerable 7.720 us 2.943 us 0.1613 us
SumList 4.062 us 2.356 us 0.1291 us

Code, for validity testing:

(Requires benchmarkdotnet nuget package to be added)

namespace ListVsEnumerable
{
    using System.Collections.Generic;
    using System.Linq;

    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Configs;
    using BenchmarkDotNet.Jobs;
    using BenchmarkDotNet.Running;

    public class Program
    {
        private static void Main(string[] args)
        {
            BenchmarkRunner.Run<Program>(
                DefaultConfig.Instance
                    .With(Job.ShortRun));
//                    .With(Job.ShortRun.With(Jit.RyuJit).With(Platform.X64)));
        }

        private const int ListLength = 1000;

        private static readonly IList<int> list = Enumerable.Range(0, ListLength).ToList();

        [Benchmark]
        public void EnumerateEnumerable()
        {
            foreach (var item in list) { }
        }

        [Benchmark]
        public void EnumerateList()
        {
            int count = list.Count;
            for (int i = 0; i < count; i++)
            {
                var item = list[i];
            }
        }

        [Benchmark]
        public long SumEnumerable()
        {
            long sum = 0;
            foreach (var item in list)
            {
                sum += item;
            }

            return sum;
        }

        [Benchmark]
        public long SumList()
        {
            int count = list.Count;
            long sum = 0;
            for (int i = 0; i < count; i++)
            {
                var item = list[i];
                sum += item;
            }

            return sum;
        }
    }
}

Do note that this has to take advantage of the optimization of pulling .Count OUT of the loop to really be worth it; if you inline those int count's to the for, the results look like this

Method Mean Error StdDev
EnumerateEnumerable 7.223 us 4.011 us 0.2199 us
EnumerateList 5.485 us 7.374 us 0.4042 us
SumEnumerable 7.175 us 8.143 us 0.4464 us
SumList 5.707 us 6.125 us 0.3357 us

This slightly suggests that perhaps IEnumerable should have returned some sort of FP style Maybe<T> in one call to optimize :)

ndrwrbgs avatar Jul 14 '19 03:07 ndrwrbgs

Do note that this has to take advantage of the optimization of pulling .Count OUT of the loop to really be worth it;

Note: this is what I have found in the past as well. BUT: This contradicts it "https://blogs.msdn.microsoft.com/clrcodegeneration/2009/08/13/array-bounds-check-elimination-in-the-clr/"

Smurf-IV avatar Jul 14 '19 07:07 Smurf-IV

yeah the bounds check probably isn’t getting eliminated when working with an enunerable plus count has function call overhead here probably. its a misoptimization with arrays and lists but good with IList. kinda silly!

anyway this would be valuable to add support for, its easy work but a lot of it. if people want to call a file they want to tackle here to help out that would be great.

jackmott avatar Jul 14 '19 13:07 jackmott

BUT: This contradicts...

It doesn't contradict, since that's not on IList. The compiler only does this when the type can be KNOWN to be an array (this is why providing overrides for Arrays themselves can be insanely beneficial)

ndrwrbgs avatar Jul 24 '19 20:07 ndrwrbgs

Currently doing something for the SumF's

Smurf-IV avatar Jul 31 '19 11:07 Smurf-IV

Currently doing AverageF's

Smurf-IV avatar Jul 31 '19 14:07 Smurf-IV

Currently doing AggregateF's image

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

First's then found this: #22

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

Changed to IReadOnlyList, so that the caller has confidence that the members will "probably" not be changed using these calls.

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

Doing LastF's

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

Doing MaxF's

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

Spans are still NOT Faster or fast enough! image

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

Doing MinF's

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

MinF's Done: image

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

Doing WhereSumF's

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

WhereSumF's Done.. Span is STILL a problem! image

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

yeah the bounds check probably isn’t getting eliminated when working with an enunerable plus count has function call overhead here probably. its a misoptimization with arrays and lists but good with IList. kinda silly!

    // Note: Lists can have items added and removed whilst these API's are in use
    // The IReadOnlyList<T> represents a list in which the _number_ and _order_ of list elements is read-only.
    //

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