Your testing code has three big problems:
- You are not testing actual query execution time, you are only measuring the time taken to create the query.
- The first test is unfair as you are adding the overhead of the loading of the assemblies.
- You are running a single pass, which doesn't make a lot of sense when testing performance.
Look at something like this instead:
var animals = new List<IAnimal>();
for (int i = 0; i < 1000000; i++)
{
animals.Add(new Bear());
animals.Add(new Cat());
}
// remove overhead of the first query
int catsCount = animals.Where(x => x == x).Count();
var whereIsTicks = new List<long>();
var whereTypeTicks = new List<long>();
var ofTypeTicks = new List<long>();
var sw = Stopwatch.StartNew();
// a performance test with a single pass doesn't make a lot of sense
for (int i = 0; i < 100; i++)
{
sw.Restart();
// Where (is) + Select
catsCount = animals.Where(_ => _ is Cat).Select(_ => (Cat)_).Count();
whereIsTicks.Add(sw.ElapsedTicks);
// Where (enum) + Select
sw.Restart();
catsCount = animals.Where(_ => _.Type == AnimalTypes.Cat).Select(_ => (Cat)_).Count();
whereTypeTicks.Add(sw.ElapsedTicks);
// OfType
sw.Restart();
catsCount = animals.OfType<Cat>().Count();
ofTypeTicks.Add(sw.ElapsedTicks);
}
sw.Stop();
// get the average run time for each test in an easy-to-print format
var results = new List<Tuple<string, double>>
{
Tuple.Create("Where (is) + Select", whereIsTicks.Average()),
Tuple.Create("Where (type) + Select", whereTypeTicks.Average()),
Tuple.Create("OfType", ofTypeTicks.Average()),
};
// print results orderer by time taken
foreach (var result in results.OrderBy(x => x.Item2))
{
Console.WriteLine($"{result.Item1} => {result.Item2}");
}
Running this multiple times, Where (is)
can be a little faster or slower than Where (type)
, however, OfType
is always the slowest by a good margin:
i < 10
:
Where (type) + Select => 111428.9
Where (is) + Select => 132695.8
OfType => 158220.7
i < 100
:
Where (is) + Select => 110541.8
Where (type) + Select => 119822.74
OfType => 150087.22
i < 1000
:
Where (type) + Select => 113196.381
Where (is) + Select => 115656.695
OfType => 160461.465
The reason why OfType
will always be slower is pretty obvious when you look at the source code for the OfType
method:
static IEnumerable<TResult> OfTypeIterator<TResult>(IEnumerable source)
{
foreach (object obj in source)
{
if (obj is TResult)
{
yield return (TResult)obj;
}
}
}
As you can see, the source items are type checked with is
and then casted back to TResult
. The difference would be bigger for value types due to the boxing.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…