gpt4 book ai didi

c# - 枚举列表比 IList、ICollection 和 IEnumerable 更快

转载 作者:太空狗 更新时间:2023-10-30 00:37:08 25 4
gpt4 key购买 nike

最近我一直在研究编写返回集合的函数的一些约定。我想知道实际使用 List<int> 的函数是否应该返回 List<int>或者更确切地说 IList<int> , ICollection<int>IEnumerable<int> .我创建了一些性能测试,我对结果感到非常惊讶。

static List<int> list = MakeList();
static IList<int> iList = MakeList();
static ICollection<int> iCollection = MakeList();
static IEnumerable<int> iEnumerable = MakeList();

public static TimeSpan Measure(Action f)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
f();
stopWatch.Stop();
return stopWatch.Elapsed;
}

public static List<int> MakeList()
{
var list = new List<int>();
for (int i = 0; i < 100; ++i)
{
list.Add(i);
}
return list;
}

public static void Main()
{
var time1 = Measure(() => { // Measure time of enumerating List<int>
for (int i = 1000000; i > 0; i-- ) {
foreach (var item in list)
{
var x = item;
}
}
});
Console.WriteLine($"List<int> time: {time1}");

var time2 = Measure(() => { // IList<int>
for (int i = 1000000; i > 0; i-- ) {
foreach (var item in iList)
{
var x = item;
}
}
});
Console.WriteLine($"IList<int> time: {time2}");

var time3 = Measure(() => { // ICollection<int>
for (int i = 1000000; i > 0; i-- ) {
foreach (var item in iCollection)
{
var x = item;
}
}
});
Console.WriteLine($"ICollection<int> time: {time3}");

var time4 = Measure(() => { // IEnumerable<int>
for (int i = 1000000; i > 0; i-- ) {
foreach (var item in iEnumerable)
{
var x = item;
}
}
});
Console.WriteLine($"IEnumerable<int> time: {time4}");
}

输出:

List<int> time: 00:00:00.7976577
IList<int> time: 00:00:01.5599382
ICollection<int> time: 00:00:01.7323919
IEnumerable<int> time: 00:00:01.6075277

我尝试了不同的措施顺序或制作 MakeList()返回上述接口(interface)之一,但所有接口(interface)仅确认返回 List<int>并将其处理为 List<int>速度大约是接口(interface)的两倍

但是各种来源,包括this answer声称你永远不应该返回List<> 并始终使用界面。

所以我的问题是:

  • 为什么要处理 List<int>大约是接口(interface)速度的两倍?
  • 如果我们关心性能,我们应该从函数返回什么以及如何管理代码?

最佳答案

Why is processing a List<int> about twice as fast as the interfaces?

好问题。当试图 foreach某些东西,C# 首先 检查集合的类型是否已经有一个名为 GetEnumerator 的方法返回具有 MoveNext 的类型和 Current .如果是,它会直接调用它们。如果不是,则返回使用 IEnumerable<T>IEnumerableIEnumerator<T>IEnumerator获取枚举器,以便它可以调用 MoveNextCurrent .

做出这种设计选择有两个原因。首先,在泛型之前的 C# 1.0 世界中,这意味着您可以调用 Current返回 int ; IEnumerator.Current当然是object所以会装箱 int ,这既是速度又是内存损失。其次,这意味着集合的作者可以做实验来找出 MoveNext 的哪个实现。和 Current表现最好。

List<T> 的实现者正是这样做的;如果你检查 GetEnumeratorList<T>你会发现一些有趣的事情:它返回一个可变值类型。是的,可变值类型被认为是一种容易被滥用的不良做法。但是因为 GetEnumerator 的这个重载使用了 99.999% foreach 代表您调用,绝大多数时候您甚至都不会注意到有一个可变值供您滥用,因此不要滥用它。

(注意:前一段的要点不应该是“使用可变值类型,因为它们很快”。要点应该是了解用户的使用模式,然后设计一个安全、高效的工具来满足他们的需求。通常可变值类型不是正确的工具。)

总之,长话短说,我们通过在迭代编译时已知为 List<T> 的对象时直接绑定(bind)到可变值类型的方法来避免各种虚拟调用、接口(interface)类型检查等。 .

What should we return from a function and how to manage the code if we care about performance?

如果您关心速度性能,那么您应该关注程序中最慢的部分。程序中最慢的是调用 MoveNext在一个集合?如果是这样,恭喜你,你有一个非常快的程序; MoveNext是接下来要优化的事情。但在这种情况下,你真的应该问“我如何完全避免或延迟这个循环?”如果你在那条船上。

如果MoveNext不是程序中最慢的东西那么谁在乎它在特定实现中是否慢了几纳秒? 返回逻辑上最接近调用者想要和需要的类型,不用担心微小的损失。

关于c# - 枚举列表比 IList、ICollection 和 IEnumerable 更快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58410905/

25 4 0