Why is this IEnumerable extension method much slower than the other (simpler) extension method (which only repeats input)?
I have a console application that contains two methods:
public static IEnumerable<TSource>
FooA<TSource>(this IEnumerable<IEnumerable<TSource>> source)
{
return source.Aggregate((x, y) => x.Intersect(y));
}
public static IEnumerable<TSource>
FooB<TSource>(this IEnumerable<IEnumerable<TSource>> source)
{
foreach (TSource element in source.First())
{
yield return element;
}
}
What it does: Both take a sequence of sequences, FooA
create a set of intersections of all of them, and then return the result. FooB
just repeat the first sequence.
What I don't understand: more than 10 times slower than , but actually much simpler (no method call ). FooB
FooA
FooB
Intersect()
Here are the results:
00:00:00.0071053 (FooA)
00:00:00.0875303 (FooB)
FooB
could be much faster by returning directly source.First()
, anyway, I decompiled the method Distinct
using ILSpy and found exactly the same return loop of foreach exit:
private static IEnumerable<TSource> DistinctIterator<TSource>
(IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
{
Set<TSource> set = new Set<TSource>(comparer);
foreach (TSource current in source)
{
if (set.Add(current))
{
yield return current;
}
}
yield break;
}
Also: in the code I am using, I cannot return source.First()
(I am getting CS1622 ). What I am showing here is actually much simpler code that I removed for debugging.
Here's the code I'm using for testing:
List<List<int>> foo = new List<List<int>>();
foo.Add(new List<int>(Enumerable.Range(0, 3000*1000)));
Stopwatch sa = new Stopwatch();
sa.Start();
List<int> la = FooA(foo).ToList();
Console.WriteLine(sa.Elapsed);
Stopwatch sb = new Stopwatch();
sb.Start();
List<int> lb = FooB(foo).ToList();
Console.WriteLine(sb.Elapsed);
source to share
The reason you are measuring such a big difference is that the call to Aggregate just returns your original list, since there are no elements to aggregate, because there is only one element in your list.
If you change it to
List<List<int>> foo = new List<List<int>>()
{
new List<int>(Enumerable.Range(0, 3000 * 1000)),
new List<int>(Enumerable.Range(0, 3000 * 1000)),
};
Just one item like you:
A: 00:00:00.0037843
B: 00:00:00.0514177
But with two points:
A: 00:00:00.2130628
B: 00:00:00.0574932
A is now much slower. The difference in the first example is due to the array allocation, which caused many more CPU cycles.
AllocationAmount AllocationKind
B 1CAE0 Small
B 21E5C Small
B 20020 Large
B 40020 Large
B 80020 Large
B 100020 Large
B 200020 Large
B 400020 Large
B 800020 Large
B 1000020 Large
A B71B20 Large
These are GC SelectocationTick ETW events that are thrown by the garbage collector. In fact, you have compared apples to oranges. Your general call did nothing at all.
source to share