LinqFaster
LinqFaster copied to clipboard
[Enhancement] Can these API's also work on IReadOnlyList<T>
I don't think there would be any performance difference since IEnumerable would still be happening behind the scenes.
IList
has a this[]
accessor ?
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.
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 :)
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/"
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.
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)
Currently doing something for the SumF's
Currently doing AverageF's
Currently doing AggregateF's
First's then found this: #22
Changed to IReadOnlyList
, so that the caller has confidence that the members will "probably" not be changed using these calls.
Doing LastF's
Doing MaxF's
Spans are still NOT Faster or fast enough!
Doing MinF's
MinF's Done:
Doing WhereSumF's
WhereSumF's Done.. Span is STILL a problem!
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.
//