循环中 Enumerable Count 方法的性能
Performance of Enumerable Count method in a loop
我有一个由 IEnumerable
表示的项目序列
我需要遍历这些项目,它应该是 for-loop
因为索引很重要。
我的问题是,以下两个选项在性能上有区别吗?
1.
for (int i = 0; i < items.Count(); i++)
{
//Do something
}
var itemsLength = items.Count();
for (int i = 0; i < itemsLength; i++)
{
//Do something
}
换句话说,方法 items.Count()
运行 是否在选项 1 的每次迭代中一次又一次?
通常当涉及到这类性能问题时,测试它会更容易。使用 BenchmarkDotnet :
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
|-------- |----------:|----------:|----------:|------:|--------:|----------:|
| Option2 | 49.40 ns | 0.700 ns | 0.654 ns | 1.00 | 0.00 | - |
| Option1 | 955.00 ns | 16.993 ns | 15.064 ns | 19.30 | 0.34 | - |
选项 1 慢了 20 倍。这里的 IEnumerable
是使用 Enumerable.Range(0,100)
生成的
然后我还测试了如果您的实际类型是 List
会发生什么,以及 Count()
是否优化为仅 return Count
属性。这些是结果:
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
|-------- |----------:|---------:|---------:|------:|--------:|----------:|
| Option2 | 40.53 ns | 0.246 ns | 0.192 ns | 1.00 | 0.00 | - |
| Option1 | 452.07 ns | 4.906 ns | 4.097 ns | 11.14 | 0.12 | - |
这次选项 1 慢了 11 倍,似乎只是因为不必生成完整的 100 个可枚举项。
使用的测试用例:
[Benchmark]
public static int Option1()
{
var x = 0;
for (int i = 0; i < Data.Count(); i++)
{
x++;
}
return x;
}
[Benchmark(Baseline = true)]
public static int Option2()
{
var itemsLength = Data.Count();
var x = 0;
for (int i = 0; i < itemsLength; i++)
{
x++;
}
return x;
}
在可枚举源上调用 Count()
将耗尽可枚举直到枚举所有元素。因此,在 for 循环中的 condition-block 中的可枚举对象上调用 Count()
将因此在每次迭代时耗尽可枚举对象。例如调用
var numbers = VerboseRange(1, 5);
for (var index = 0; index < numbers.Count(); index++)
{
Console.WriteLine($"For-loop is at index {index}...");
}
IEnumerable<int> VerboseRange(int start, int count)
{
foreach (var number in Enumerable.Range(start, count))
{
Console.WriteLine($"Yielded number {number}.");
yield return number;
}
}
会输出
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 0...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 1...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 2...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 3...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 4...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
所以还是先数再说比较好
但是,我建议您使用计数器和 foreach
-loop
var count = 0;
foreach (var item in items)
{
// do something
count++;
}
在 C# 7.0 中,您终于可以做到
foreach (var (item, index) in items.WithIndex())
{
// do something
}
我有一个由 IEnumerable
我需要遍历这些项目,它应该是 for-loop
因为索引很重要。
我的问题是,以下两个选项在性能上有区别吗?
1.
for (int i = 0; i < items.Count(); i++)
{
//Do something
}
var itemsLength = items.Count();
for (int i = 0; i < itemsLength; i++)
{
//Do something
}
换句话说,方法 items.Count()
运行 是否在选项 1 的每次迭代中一次又一次?
通常当涉及到这类性能问题时,测试它会更容易。使用 BenchmarkDotnet :
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
|-------- |----------:|----------:|----------:|------:|--------:|----------:|
| Option2 | 49.40 ns | 0.700 ns | 0.654 ns | 1.00 | 0.00 | - |
| Option1 | 955.00 ns | 16.993 ns | 15.064 ns | 19.30 | 0.34 | - |
选项 1 慢了 20 倍。这里的 IEnumerable
是使用 Enumerable.Range(0,100)
然后我还测试了如果您的实际类型是 List
会发生什么,以及 Count()
是否优化为仅 return Count
属性。这些是结果:
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
|-------- |----------:|---------:|---------:|------:|--------:|----------:|
| Option2 | 40.53 ns | 0.246 ns | 0.192 ns | 1.00 | 0.00 | - |
| Option1 | 452.07 ns | 4.906 ns | 4.097 ns | 11.14 | 0.12 | - |
这次选项 1 慢了 11 倍,似乎只是因为不必生成完整的 100 个可枚举项。
使用的测试用例:
[Benchmark]
public static int Option1()
{
var x = 0;
for (int i = 0; i < Data.Count(); i++)
{
x++;
}
return x;
}
[Benchmark(Baseline = true)]
public static int Option2()
{
var itemsLength = Data.Count();
var x = 0;
for (int i = 0; i < itemsLength; i++)
{
x++;
}
return x;
}
在可枚举源上调用 Count()
将耗尽可枚举直到枚举所有元素。因此,在 for 循环中的 condition-block 中的可枚举对象上调用 Count()
将因此在每次迭代时耗尽可枚举对象。例如调用
var numbers = VerboseRange(1, 5);
for (var index = 0; index < numbers.Count(); index++)
{
Console.WriteLine($"For-loop is at index {index}...");
}
IEnumerable<int> VerboseRange(int start, int count)
{
foreach (var number in Enumerable.Range(start, count))
{
Console.WriteLine($"Yielded number {number}.");
yield return number;
}
}
会输出
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 0...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 1...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 2...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 3...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 4...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
所以还是先数再说比较好
但是,我建议您使用计数器和 foreach
-loop
var count = 0;
foreach (var item in items)
{
// do something
count++;
}
在 C# 7.0 中,您终于可以做到
foreach (var (item, index) in items.WithIndex())
{
// do something
}