2 回答

TA贡献1998条经验 获得超6个赞
如果您使用产量测量版本而不具体化列表,它将比其他版本具有优势,因为它不必分配和调整大列表的大小(以及触发 GC)。
根据您的编辑,我想添加以下内容:
但是,请记住,从语义上讲,您正在查看两种不同的方法。一个产生一个集合。它的大小是有限的,您可以存储对集合的引用、更改其元素并共享它。
另一个产生一个序列。它可能是无限的,每次迭代它都会得到一个新副本,并且它后面可能有也可能没有集合。
它们不是同一件事。编译器不会创建集合来实现序列。如果您通过在幕后实现集合来实现序列,您将看到与使用列表的版本相似的性能。
BenchmarkDotNet 默认情况下不允许您对延迟执行进行计时,因此您必须构建一个使用我在下面所做的方法的测试。我通过 BenchmarkDotNet 运行它并得到以下结果。
Method | Mean | Error | StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
------------- |---------:|---------:|---------:|------------:|------------:|------------:|--------------------:|
ConsumeYield | 475.5 us | 7.010 us | 6.214 us | - | - | - | 40 B |
ConsumeList | 958.9 us | 7.271 us | 6.801 us | 285.1563 | 285.1563 | 285.1563 | 1049024 B |
注意分配。在某些情况下,这可能会有所不同。
我们可以通过分配正确的大小列表来抵消一些分配,但最终这不是苹果对苹果的比较。下面的数字。
Method | Mean | Error | StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
------------- |---------:|----------:|----------:|------------:|------------:|------------:|--------------------:|
ConsumeYield | 470.8 us | 2.508 us | 2.346 us | - | - | - | 40 B |
ConsumeList | 836.2 us | 13.456 us | 12.587 us | 124.0234 | 124.0234 | 124.0234 | 400104 B |
代码如下。
[MemoryDiagnoser]
public class Test
{
static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<Test>();
}
public int Size = 100000;
[Benchmark]
public int ConsumeYield()
{
var sum = 0;
foreach (var x in CreateNumbersYield()) sum += x;
return sum;
}
[Benchmark]
public int ConsumeList()
{
var sum = 0;
foreach (var x in CreateNumbersList()) sum += x;
return sum;
}
public IEnumerable<int> CreateNumbersYield() //for yield
{
for (int i = 0; i < Size; i++) yield return i;
}
public IEnumerable<int> CreateNumbersList() //for list
{
var list = new List<int>();
for (int i = 0; i < Size; i++) list.Add(i);
return list;
}
}

TA贡献1836条经验 获得超4个赞
您必须考虑以下几点:
List<T>
消耗内存,但您可以一次又一次地迭代它而无需任何额外资源。为了达到同样的效果yield
,您需要通过 实现序列ToList()
。生产时最好设置容量
List<T>
。这将避免内部数组调整大小。
这是我所拥有的:
class Program
{
static void Main(string[] args)
{
// warming up
CreateNumbersYield(1);
CreateNumbersList(1, true);
Measure(null, () => { });
// testing
var size = 1000000;
Measure("Yield", () => CreateNumbersYield(size));
Measure("Yield + ToList", () => CreateNumbersYield(size).ToList());
Measure("List", () => CreateNumbersList(size, false));
Measure("List + Set initial capacity", () => CreateNumbersList(size, true));
Console.ReadLine();
}
static void Measure(string testName, Action action)
{
var sw = new Stopwatch();
sw.Start();
action();
sw.Stop();
Console.WriteLine($"{testName} completed in {sw.Elapsed}");
}
static IEnumerable<int> CreateNumbersYield(int size) //for yield
{
for (int i = 0; i < size; i++)
{
yield return i;
}
}
static IEnumerable<int> CreateNumbersList(int size, bool setInitialCapacity) //for list
{
var list = setInitialCapacity ? new List<int>(size) : new List<int>();
for (int i = 0; i < size; i++)
{
list.Add(i);
}
return list;
}
}
结果(发布版本):
Yield completed in 00:00:00.0001683
Yield + ToList completed in 00:00:00.0121015
List completed in 00:00:00.0060071
List + Set initial capacity completed in 00:00:00.0033668
如果我们比较可比较的情况(Yield + ToList& List + Set initial capacity),yield速度要慢得多。
- 2 回答
- 0 关注
- 106 浏览
添加回答
举报