所以,在性能方面,首个(FirstOrDefault(predicate))更好1
我明白,我也认为多一个方法调用应该会稍微慢一点,这只是我的直觉。尽管如此,我决定运行一些基准测试来证明我的正确性,但是我毫不知情。
这是我运行基准测试的结果:
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-3630QM CPU 2.40GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
[Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
Job-XMZTSC : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT
Runtime=.NET 4.7.2
Method N Mean Error StdDev Ratio RatioSD
WherePlusFirstOrDefaultArray 10000 31.44 us 0.288 us 0.270 us 0.40 0.00
FirstOrDefaultArray 10000 78.47 us 0.679 us 0.635 us 1.00 0.00
WherePlusFirstOrDefaultList 10000 54.27 us 1.070 us 1.099 us 0.69 0.02
FirstOrDefaultList 10000 100.84 us 1.722 us 1.611 us 1.29 0.02
WherePlusFirstOrDefaultArray 100000 325.41 us 4.840 us 4.527 us 0.39 0.01
FirstOrDefaultArray 100000 829.85 us 16.513 us 15.446 us 1.00 0.00
WherePlusFirstOrDefaultList 100000 558.10 us 5.507 us 5.151 us 0.67 0.01
FirstOrDefaultList 100000 1,026.93 us 17.648 us 16.508 us 1.24 0.02
WherePlusFirstOrDefaultArray 1000000 3,255.46 us 9.615 us 7.507 us 0.40 0.01
FirstOrDefaultArray 1000000 8,134.15 us 108.425 us 101.420 us 1.00 0.00
WherePlusFirstOrDefaultList 1000000 5,477.63 us 70.584 us 66.024 us 0.67 0.01
FirstOrDefaultList 1000000 9,987.54 us 64.239 us 60.089 us 1.23 0.02
Where(predicate).FirstOrDefault()
不仅更快,而且优势有多大。
这是我使用 BenchmarkDotNet
的基准测试代码
[SimpleJob(RuntimeMoniker.Net472)]
public class Benchmarks
{
private int[] _array;
private List<int> _list;
[Params(10000, 100000, 1000000)]
public int N;
[GlobalSetup]
public void Setup()
{
_array = new int[N];
_list = new List<int>(N);
_array = Enumerable
.Repeat(0, N - 1).ToArray();
_list = Enumerable
.Repeat(0, N - 1).ToList();
_array[N - 2] = 7;
_list[N - 2] = 7;
}
[Benchmark]
public int WherePlusFirstOrDefaultArray()
{
var seven = _array.Where(n => n == 7).FirstOrDefault();
return seven;
}
[Benchmark(Baseline = true)]
public int FirstOrDefaultArray()
{
var seven = _array.FirstOrDefault(n => n == 7);
return seven;
}
[Benchmark]
public int WherePlusFirstOrDefaultList()
{
var seven = _list.Where(n => n == 7).FirstOrDefault();
return seven;
}
[Benchmark]
public int FirstOrDefaultList()
{
var seven = _list.FirstOrDefault(n => n == 7);
return seven;
}
}
由于结果让我感到惊讶,没有其他选择,只能问你们我做错了什么或者是我漏掉了什么?
编辑:
我为了让大家更好地理解,添加了有关数组和列表结构的基准测试,以便帮助大家找到问题所在。
编辑2: 故事还在继续,我认为我离答案更近了。将硬件计数器添加到我的基准测试中产生了以下有趣的结果:
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-3630QM CPU 2.40GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
[Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
Job-ZTIMEH : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT
Runtime=.NET 4.7.2
Method N Mean Error StdDev Ratio RatioSD CacheMisses/Op BranchMispredictions/Op
WherePlusFirstOrDefaultArray 1000000 3.222 ms 0.0224 ms 0.0210 ms 0.39 0.01 885 327
FirstOrDefaultArray 1000000 8.166 ms 0.1992 ms 0.1863 ms 1.00 0.00 1,795 810
WherePlusFirstOrDefaultList 1000000 5.564 ms 0.1066 ms 0.1228 ms 0.68 0.02 1,051 503
FirstOrDefaultList 1000000 10.161 ms 0.1816 ms 0.1699 ms 1.24 0.03 3,451 1,442
由于某种原因,我仍然无法解释为什么
FirstOrDefault(predicate)
方法比 Where(predicate).FirstOrDefault()
方法产生了2到3倍的分支预测错误和缓存未命中,这肯定在我之前观察到的结果中起了一定作用。另外,有一个奇怪的事情,如果你查看
FirstOrDefaultArray
和 FirstOrDefaultList
的结果并进行比较,你会发现列表要慢24%,但是这些方法生成的程序集在我的看来是相同的:https://www.diffchecker.com/WSjAQlet(我剥离了指令的内存地址)。
IEnumerable<int>
而不是List<int>
,它们的性能特征将大致相同。使用Where
将根据传入的可枚举类型选择一个中间迭代器,我猜List<T>
在这方面具有优越的性能特征而不是IEnumerable<T>
。FirstOrDefault(predicate)
本质上只是对IEnumerable<T>
进行 foreach,因此它无法利用您拥有 List 的事实。 - Jonathon ChaseList
会有优势。在这个基准测试中,我期望FirstOrDefault
能够以一些非常小的边际获胜,因为它需要额外的方法调用,但绝对不是我已经得到的结果。 - kuskmen.Where(condition).FirstOrDefault()
相对于FirstOrDefault(condition)
确实具有性能优势。我不得不运行自己的测试,我认为它们之间的差异不会太大,但我的发现支持你所看到的。我还尝试了这些操作与数组,操作比使用List<int>
明显更快,但Where
仍然是约2倍的速度。这是最有趣的考虑。像EntityFramework这样的解释器,两者都归结为相同的SQL,因此没有区别。但在内存中确实存在明显的差异。 - Steve Py