【问题标题】:OfType(x) vs Where(_ => _ is x) vs Where with enumOfType(x) vs Where(_ => _ is x) vs Where with enum
【发布时间】:2018-07-01 11:55:56
【问题描述】:

我对这里的结果有点困惑,也许有人可以给我一些见解?

基本上,我正在尝试测试使用之间的性能

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

以下是课程:

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;
}

编辑:此代码是基于 cmets 修复的!抱歉错误 这是测试方法

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();
}

奇怪的是,结果总是确保最后一次测试得到最好的结果......

【问题讨论】:

  • Emh...您只是在创建查询,而不是实际执行它们。在每个查询的末尾添加.ToArray()
  • 请注意type == type 是精确的类型比较,而is/OfType 支持子类(例如 Siamese 是 Cat,OfType() 和 is Cat 都会返回暹罗猫)
  • 这段代码实际上是在测量 jitting 开销,第一个总是很昂贵。始终重复测试至少 10 次,您会看到这种开销消失了。感受一下数字,2 滴答声太快了。否则通过将集合大小加倍很容易看到。谷歌“linq 延迟执行”以了解更多信息。一旦你修复它,当心像这样非常快速的代码的嘈杂结果,你应该看到类型比较是最快的。应该是,不需要像 is 和 OfType 那样检查类型层次需要做的。
  • 谢谢大家,我更新了我的代码,但仍然得到不寻常的结果。
  • 不确定否决票。请告诉我如何改进问题?

标签: c#


【解决方案1】:

您的测试代码存在三个大问题:

  1. 您不是在测试实际查询执行时间,您只是在测量创建查询所花费的时间。
  2. 第一个测试是不公平的,因为您增加了加载程序集的开销。
  3. 您正在运行单次通过,这在测试性能时没有多大意义。

看看这样的东西:

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}");
}

多次运行,Where (is) 可能比 Where (type) 快​​一点或慢一点,但是,OfType 始终是最慢的:

  1. i &lt; 10:

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

    Where (is) + Select => 110541.8
    Where (type) + Select => 119822.74
    OfType => 150087.22
    
  3. i &lt; 1000:

    Where (type) + Select => 113196.381
    Where (is) + Select => 115656.695
    OfType => 160461.465
    

查看源代码for the OfType methodOfType 总是变慢的原因很明显:

static IEnumerable<TResult> OfTypeIterator<TResult>(IEnumerable source) 
{
    foreach (object obj in source) 
    {
        if (obj is TResult) 
        {
            yield return (TResult)obj;
        }
    }
}

如您所见,源项目使用is 进行类型检查,然后转换回TResult。由于装箱,值类型的差异会更大。

【讨论】:

  • 谢谢。所以“赢家”是哪里(是)。这是出乎意料的。
  • @Svek 正如我所提到的,并非总是如此,有时 Where (type) 更快。请注意,尽管结果存在巨大差异,因为 Enum 不允许您检查孩子,而 is 会,正如对问题的评论中提到的那样
  • @Svek 欢迎您。请参阅我的更新以获取说明
  • @CamiloTerevinto 在进行测试时,您不希望 GC 运行...因此,如果可以,您应该尽量减少内存分配。而不是.ToArray();(这将创建两个集合,一个内部加上数组),使用.Count(),它不会分配任何东西。
  • “如您所见,源项目已装箱” - 不,这里根本没有装箱。这些元素已经是参考。只有在涉及值类型时才会涉及装箱。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-23
  • 1970-01-01
  • 2011-01-21
  • 2023-03-04
  • 2018-02-18
相关资源
最近更新 更多