Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
165 views
in Technique[技术] by (71.8m points)

c# - OfType(x) vs Where(_ => _ is x) vs Where with enum

I'm a bit confused with the result here, maybe someone could give me some insights?

Basically, I am trying to test the performance between using

  • OfType(x)
  • Where(_ = _ is x).Select((X)x)
  • Where(_ = _.Type = type).Select((X)x)

Here are the classes:

public enum AnimalTypes { Alligator, Bear, Cat, Dog, Elephant }

public interface IAnimal
{
    AnimalTypes Type { get; }
}

public class Bear : IAnimal
{
    public AnimalTypes Type => AnimalTypes.Bear;
}

public class Cat : IAnimal
{
    public AnimalTypes Type => AnimalTypes.Cat;
}

edit: this code was fixed based on the comments! sorry for the error and here is the testing method

void Main()
{
    List<IAnimal> animals = new List<IAnimal>();

    for (int i = 0; i < 100000; i++)
    {
        animals.Add(new Bear());
        animals.Add(new Cat());
    }


    // tests
    IEnumerable<Cat> test1 = animals.OfType<Cat>();
    IEnumerable<Cat> test2 = animals.Where(_ => _ is Cat).Select(_ => (Cat)_);
    IEnumerable<Cat> test3 = animals.Where(_ => _.Type == AnimalTypes.Cat).Select(_ => (Cat)_);

    Stopwatch sw = new Stopwatch();

    // OfType       
    sw.Start();
    test1.ToArray();
    sw.Stop();
    Console.WriteLine($"OfType = {sw.ElapsedTicks} ticks");
    sw.Reset();

    // Where (is) + Select
    sw.Start();
    test2.ToArray();
    sw.Stop();
    Console.WriteLine($"Where (is) + Select = {sw.ElapsedTicks} ticks");
    sw.Reset();

    // Where (enum) + Select
    sw.Start();
    test3.ToArray();
    sw.Stop();
    Console.WriteLine($"Where (type) + Select = {sw.ElapsedTicks} ticks");
    sw.Reset();
}

Oddly, the results always ensure that the last test gets the best results...

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Your testing code has three big problems:

  1. You are not testing actual query execution time, you are only measuring the time taken to create the query.
  2. The first test is unfair as you are adding the overhead of the loading of the assemblies.
  3. 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:

  1. i < 10:

    Where (type) + Select => 111428.9
    Where (is) + Select => 132695.8
    OfType => 158220.7
    
  2. i < 100:

    Where (is) + Select => 110541.8
    Where (type) + Select => 119822.74
    OfType => 150087.22
    
  3. 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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...