在C#中优化列表性能

时间:2015-06-09 19:00:48

标签: c# performance linq list hashset

我正在研究一个项目(在.NET 3.5中),该项目读入2个文件,然后比较它们并找到丢失的对象。

根据这些数据,我需要进一步解析它并找到对象位置。我会尝试进一步解释:

我有2个名单: 1列表是服务器上所有文件的一个很长的列表,以及它们在服务器或其他服务器上的物理地址,这个文件长度超过10亿行并且不断增长(我知道这个文件更为荒谬)。目前文件大小约为160MB。 另一个列表是一个报告列表,显示服务器上缺少的文件。与列表1相比,此列表微不足道,通常小于1MB。

我必须将列表2与列表1相交,并确定丢失的对象所在的位置。列表中的项目看起来像这样(不幸的是它是空格分隔的而不是CSV文档): filename.extension rev rev#source server:harddriveLocation \ | filenameOnServer.extension origin

使用流,我将两个文件读入单独的字符串列表。然后我拿一个正则表达式并将列表2中的项解析成包含filename.extension,rev和rev#的第三个列表。所有这一切都非常有效,它的表现正在扼杀我。

我希望有一种更有效的方式来做我正在做的事情。

Orange

这样可行,但由于我的遗失物品清单中目前缺少1300件物品,因此平均需要8到12分钟才能完成。花费时间最长的部分是

foreach (String item in slMissingObjectReport)
{
    if (item.Contains(".ext1") || item.Contains(".ext2") || item.Contains(".ext3"))
    {
        if (!item.Contains("|"))
        {                                     
            slMissingObjects.Add(item + "," + slMissingObjectReport[i + 1] + "," + slMissingObjectReport[i + 2]); //object, rev, version
        }
    }

    i++;
}

int j = 1; //debug only

foreach (String item in slMissingObjects)
{
    IEnumerable<String> found = Enumerable.Empty<String>();
    Stopwatch matchTime = new Stopwatch(); //used for debugging
    matchTime.Start(); //start the stop watch

    foreach (String items in slAllObjects.Where(s => s.Contains(item.Remove(item.IndexOf(',')))))
    {
        slFoundInAllObjects.Add(item);
    }

matchTime.Stop();

tsStatus.Text = "Missing Object Count: " + slMissingObjects.Count + " | " + "All Objects count: " + slAllObjects.Count + " | Time elapsed: " + (taskTime.ElapsedMilliseconds) * 0.001 + "s | Items left: " + (slMissingObjects.Count - j).ToString();

j++;
}

taskTime.Stop();
lstStatus.Items.Add(("Time to complete all tasks: " + (taskTime.ElapsedMilliseconds) * 0.001) + "s");

我只需要一个指向正确方向的点,也许还有一个关于如何改进我正在研究的代码的方法。 LINQ似乎并不是杀手锏,它将它添加到一个似乎会破坏性能的列表中。

5 个答案:

答案 0 :(得分:4)

Hashsets专为此类任务而设计,您可以在其中使用唯一值进行比较。

列表,不是。它们只是任意的集合。

我的第一个停靠点是使用HashSet&lt;&gt;以及随之而来的各种交叉方法。

答案 1 :(得分:2)

您可以使用AddRange代替Add进行改进。 AddRange将允许内部列表预先分配添加所需的内存,而不是在foreach循环的整个过程中多次。

IEnumerable<string> items = slAllObjects.Where(s => s.Contains(item.Remove(item.IndexOf(','));
slFoundInAllObjects.AddRange(items);

其次,您应该避免item.Remove(item.IndexOf(',') lambda中的Where,因为这会导致它对列表中的每个项目执行一次。这个值是静态的,你可以提前做一次。

var itemWithoutComma = item.Remove(item.IndexOf(','));
IEnumerable<string> items = slAllObjects.Where(s => s.Contains(itemWithoutComma));
slFoundInAllObjects.AddRange(items);

答案 2 :(得分:1)

似乎已经指出了一些瓶颈。

如果我理解你是:

  1. 将两个文件读入2个列表。 O(K)
  2. 迭代一个列表(O(n))并搜索另一个列表中的匹配项(O(m))。
  3. 创建包含这些匹配项的新列表。 (O(n))的
  4. 所以你有点订单:O(K + m * n * n)。 瓶颈发生在第2步和第3步(代码中的内部循环)。

    解决方案:

    1. 您正在搜索的集合(我认为 slAllObjects )应该是您可以快速搜索的内容,因此要么使用哈希集,要么对此进行排序,并使用二进制搜索来查找此集合中的项目。
    2. 预分配您正在创建的列表。您事先知道尺寸,因此请将容量设置为匹配。
    3. 如果您使用哈希集,则此解决方案应将O(n^2) * O(m)减少为O(n) * O(k),如果您对列表进行排序,则应将O(n) * log(m)减少。

答案 3 :(得分:0)

首先停止,不要使用List。使用HashSets可以更快地插入和比较。

接下来,确定列表是否处于预先排序的顺序,如果是,则可以同时快速读取这两个文件,并且只进行一次通过,而不必将它们保存在内存中一点都不。

如果所有其他方法都失败了,请考虑使用LINQ的Intersects方法,该方法可能比你的本土版本更好。

答案 4 :(得分:0)

除了已经提出的建议外,我还会考虑使用树木。如果我理解正确,文件名中有某种层次结构(即:服务器,文件路径,文件名等),对吗?通过使用树,您可以在每个步骤中减少很多搜索空间。

此外,如果您在每个节点中使用Dictionary<String, Node>,则可以减少搜索时间,考虑到常量的层次结构级别,搜索时间将变为O(1)

此外,如果您决定使用数组或数组列表,请避免使用foreach并使用for,因为它应该更快(不使用迭代器,因此,对于数组列表至少应该更快)

如果有什么不清楚,请告诉我。