【问题标题】:Simple List<string> vs IEnumarble<string> Performance issues简单列表<string> vs IEnumerable<string> 性能问题
【发布时间】:2012-12-16 07:15:11
【问题描述】:

我已经测试了 List&lt;string&gt;IEnumerable&lt;string&gt; 使用 forforeach 循环进行迭代,列表是否可能更快?

这是我能找到的几个链接中的 2 个,这些链接公开表明迭代 IEnumerable 优于 List

Link1 Link2

我的测试是从一个包含 URL 列表的文本文件中加载 10K 行。

我首先将它加载到 List 中,然后将 List 复制到 IEnumerable

List<string> StrByLst = ...method to load records from the file .
IEnumerable StrsByIE =  StrByLst;

所以每个都有 10k 个项目类型 &lt;string&gt;

在每个集合上循环 100 次,即 100K 次迭代,结果是

List&lt;string&gt;IEnumerable&lt;string&gt; 快了惊人的50 x

这是可以预测的吗?

  • 更新

这是进行测试的代码

string WorkDirtPath = HostingEnvironment.ApplicationPhysicalPath;
    string fileName = "tst.txt";
    string fileToLoad = Path.Combine(WorkDirtPath, fileName);
    List<string> ListfromStream = new List<string>();
    ListfromStream =  PopulateListStrwithAnyFile(fileToLoad) ;
    IEnumerable<string> IEnumFromStream = ListfromStream ;

    string trslt = "";
    Stopwatch SwFr = new Stopwatch();
    Stopwatch SwFe = new Stopwatch();

    string resultFrLst = "",resultFrIEnumrable, resultFe = "", Container = "";

    SwFr.Start();

    for (int itr = 0; itr < 100; itr++)
    {
        for (int i = 0; i < ListfromStream.Count(); i++)
        {
            Container = ListfromStream.ElementAt(i);
        }
    //the stop() was here , i was doing changes , so my mistake.
    }

   SwFr.Stop();
   resultFrLst = SwFr.Elapsed.ToString();
   //forgot to do this reset though still it is faster (x56??)
   SwFr.Reset();
   SwFr.Start();
        for(int itr = 0; itr<100; itr++)
        {
            for (int i = 0; i < IEnumFromStream.Count(); i++)
            {
                Container = IEnumFromStream.ElementAt(i);
            }
        }
    SwFr.Stop();
    resultFrIEnumrable = SwFr.Elapsed.ToString();

更新...最终

将计数器移到 for 循环之外,

int counter = ..count用于 IEnumerable 和 List

然后按照@ScottChamberlain 的建议将 counter(int) 作为项目总数传递。 重新检查所有东西都已到位,现在结果比 IEnumerable 快 5 %。 因此得出结论,按场景使用 - 用例......完全没有性能差异......

【问题讨论】:

  • 请发布实际代码....
  • @MitchWheat 会做......现在
  • 那么,每次调用 SwFr.Elapsed 时您会得到什么值,这是一个调试版本吗?
  • 我认为我不会太相信继续解释Differences between EXE and DLL 的信息来源。但值得指出的是,您实际上并没有枚举列表,而是每次都在i 获取元素,而不是要求.Next()
  • @nick_w 我认为 10k 件物品还不够,我在内部结束后放置了一个外部循环将其关闭,但将塞子留在内部 - 外部内部(10k 件物品一次)。

标签: c# performance collections


【解决方案1】:

你做错了什么。

你得到的时间应该非常接近,因为你运行的是基本相同的代码。

IEnumerable 只是 List 实现的一个接口,所以当你在 IEnumerable 引用上调用某个方法时,它最终会调用 List 的相应方法。

在 IEnumerable 中没有实现代码 - 这就是接口 - 它们只指定类应该具有的功能,但没有说明它是如何实现的。

【讨论】:

  • +1 提供的代码执行完全相同。 OP 还有其他导致性能差异的原因。
  • 是的,我也做了一些改变,放置了外循环,在第一次完成内循环之后没有注意那个更接近的“}”,我的错误。我已经迟到了。下次不要在压力下工作,这是我的建议。
【解决方案2】:

您的测试有一些问题,一个是for 循环内的IEnumFromStream.Count(),每次它想要获取该值时,它必须枚举整个列表以获取计数并且该值未缓存循环之间。将该调用移出for 循环并将结果保存在int 中并将该值用于for 循环,您将看到IEnumerable 的时间更短。

此外,IEnumFromStream.ElementAt(i) 的行为类似于Count(),它必须遍历整个列表直到i(例如:第一次是0,第二次是0,1,第三次是0,1,2,等等on...) 每次List 可以直接跳转到它需要的索引。您应该改用从GetEnumerator() 返回的IEnumerator

IEnumerablefor 循环不能很好地混合。为工作使用正确的工具,或者调用 GetEnumerator() 并使用它,或者在 foreach 循环中使用它。


现在,我知道你们很多人可能会说“但它是一个接口,它只是映射调用,应该没有区别”,但有一个关键点,IEnumerable&lt;T&gt; 没有Count()ElementAt() 方法!。这些方法是 LINQ 添加的扩展方法,并且 LINQ 类不知道底层集合是一个 List,所以它会做它知道底层对象可以做的事情,那就是每次调用该方法时都遍历列表。


IEnumerable 使用 IEnumerator

using(var enu = IEnumFromStream.GetEnumerator())
{
    //You have to call "MoveNext()" once before getting "Current" the first time,
    //   this is done so you can have a nice clean while loop like this.
    while(enu.MoveNext())
    {
        Container = enu.Current;
    }
}

上面的代码和上面的代码基本一样

foreach(var enu in IEnumFromStream)
{
    Container = enu;
}

要记住的重要一点是IEnumerable 没有长度,实际上它们可以无限长。计算机科学有a whole field 检测无限长的IEnumerable

【讨论】:

  • 我认为您的最后一次编辑是关键点:CountElementAt 都是作为IEnumerable 的扩展方法实现的,因此测试之间的结果应该不会有很大差异。毕竟,OP 在两次运行中都使用了这两种方法。如果第一个测试使用Count 属性并使用ListfromStream[i] 访问该项目,我会预料到差异。
  • @ScottChamberlain 能否请您编写一些示例代码以在IEnumerable 上进行迭代,您描述的方式以及关键是什么/关键区别是什么?
  • @LoneXcoder 我添加了一个示例,显示IEnumerable 的原始用法,但我只会使用foreach
  • @ScottChamberlain 很高兴知道,我会熟悉它的特征,感谢您提供额外的信息
【解决方案3】:

根据您发布的代码,我认为问题在于您使用 Stopwatch 类。

您声明了其中两个,SwFrSwFe,但只使用前者。因此,对SwFr.Elapsed 的最后一次调用将获得两组for 循环的总时间。

如果您想以这种方式重用该对象,请在resultFrLst = SwFr.Elapsed.ToString(); 之后立即调用SwFr.Reset()

或者,您可以在运行第二个测试时使用SwFe

【讨论】:

  • swFe 瞄准了后来的测试 ForEach...我有很大的缺陷,我在进行最终结论之前没有正确检查,我不得不去某个地方,所以只有当我回来检查我的代码时,我在没有外部 x100 的前一次测试之后发现了我离开 Stop() 的错误位置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-02
  • 2016-01-06
  • 2015-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多