【问题标题】:Adding 90000 XElement to XDocument将 90000 XElement 添加到 XDocument
【发布时间】:2011-01-27 18:26:23
【问题描述】:

我有一个Dictionary<int, MyClass>

它包含 100,000 个项目

10,000 个项目值已填充,而 90,000 个为空。

我有这个代码:

var nullitems = MyInfoCollection.Where(x => x.Value == null).ToList();
nullitems.ForEach(x => LogMissedSequenceError(x.Key + 1));

private void LogMissedSequenceError(long SequenceNumber)
        {
            DateTime recordTime = DateTime.Now;

            var errors = MyXDocument.Descendants("ERRORS").FirstOrDefault();
            if (errors != null)
            {

                errors.Add(
                    new XElement("ERROR",
                        new XElement("DATETIME", DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss:fff")),
                        new XElement("DETAIL", "No information was read for expected sequence number " + SequenceNumber),
                        new XAttribute("TYPE", "MISSED"),
                        new XElement("PAGEID", SequenceNumber)
                        )
                );
            }
        }

这似乎需要大约 2 分钟才能完成。我似乎找不到瓶颈可能在哪里,或者这个时间听起来是否正确?

谁能看出为什么要花这么长时间?

【问题讨论】:

  • 使用调试跟踪查看查询需要多长时间,以及日志记录需要多长时间。此外,在处理 100,000 项文档时,XML 可能会产生大量处理开销。
  • 您是否运行了分析器?我很想知道你为什么运行 DateTime.now 两次(因为调用它相对昂贵)以及调用 90k 次时它到底有多慢......
  • 我删除了 DateTime 并且只调用它一次并将其作为字符串传递。我没有 Visual Studio Team 等,其中包含 Profiler。
  • 如果您检查一下,您可能会发现在每次调用时查找“ERRORS”元素是最昂贵的部分之一。或者将 ERRORS 元素传递给方法,或者使用 LINQ 来发挥您的优势,如下所述。

标签: c# linq .net-3.5 c#-3.0 linq-to-xml


【解决方案1】:

这是我最有可能做的。

private void BuildErrorNodes()
{
    const string nodeFormat = @"<ERROR TYPE=""MISSED""><DATETIME>{0}</DATETIME><DETAIL>No information was read for expected sequence number {1}</DETAIL><PAGEID>{1}</PAGEID></ERROR>";

    var sb = new StringBuilder("<ERRORS>");
    foreach (var item in MyInfoCollection)
    {
        if (item.Value == null) 
        {
            sb.AppendFormat(
                nodeFormat,
                DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss:fff"),
                item.Key + 1
            );
        }
    }

    sb.Append("</ERRORS>");

    var errorsNode = MyXDocument.Descendants("ERRORS").FirstOrDefault();
    errorsNode.ReplaceWith(XElement.Parse(sb.ToString()));
}

【讨论】:

    【解决方案2】:

    如果您的MyInfoCollection 很大,我不会调用ToList(),这样您就可以使用ForEach 扩展方法。调用ToList() 将创建并填充一个巨大的列表。我会删除ToList() 调用,并将.ForEach 转换为for each 语句,或者为IEnumerable&lt;T&gt; 编写一个.ForEach 扩展方法。

    然后分析它,看看需要多长时间。另一件事是删除ERRORS 元素的查找和空检查。如果它不存在,那么不要调用上面的for each 语句。这样你就可以对它进行一次空检查,而不是 90,000 次。

    另外,正如 Michael Stum 所指出的,我会定义一个字符串来保存值 DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss:fff"),然后引用它或将其传入。另外,您甚至不使用此调用:

    DateTime recordTime = DateTime.Now;
    

    【讨论】:

    • 到目前为止,我只是实现了 foreach 循环而不是 ForEach 扩展方法,它们的速度似乎相同。 ToList() 很快,只是记录需要时间。
    【解决方案3】:

    用 LINQ 查询替换方法调用怎么样?

    static void Main(string[] args)
    {
    
        var MyInfoCollection = (from key in Enumerable.Range(0, 100000)
                                let value = (MoreRandom() % 10 != 0)
                                                        ? (string)null
                                                        : "H"
                                select new { Value = value, Key = key }
                               ).ToDictionary(k => k.Key, v => v.Value);
    
        var MyXDocument = new XElement("ROOT",
                                        new XElement("ERRORS")
                                      );
        var sw = Stopwatch.StartNew();
        //===
        var errorTime = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss:fff");
        var addedIndex = MyInfoCollection.Select((item, index) =>
                                                        new
                                                        {
                                                            Value = item.Value,
                                                            Key = item.Key,
                                                            Index = index
                                                        });
        var errorQuery = from item in addedIndex
                         where string.IsNullOrEmpty(item.Value)
                         let sequenceNumber = item.Key + 1
                         let detail = "No information was read for expected " +
                                      "sequence number " + sequenceNumber
                         select new XElement("ERROR",
                            new XElement("DATETIME", errorTime),
                            new XElement("DETAIL", detail),
                            new XAttribute("TYPE", "MISSED"),
                            new XElement("PAGEID", sequenceNumber)
                            );
    
        var errors = MyXDocument.Descendants("ERRORS").FirstOrDefault();
        if (errors != null)
            errors.Add(errorQuery);
        //===
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds); //623
    }
    static RandomNumberGenerator rand = RandomNumberGenerator.Create();
    static int MoreRandom()
    {
        var buff = new byte[1];
        rand.GetBytes(buff);
        return buff[0];
    }
    

    【讨论】:

    • @Jon:“let”允许您分配上下文变量。这就像在 foreach 循环中创建一个变量赋值。您可以将相同的信息放在“select”语句中,但我发现这个模型更容易理解和重用。
    • 我还在 LINQ 查询中使用 DateTime.Now 进行了测试。它确实稍微减慢了这个过程,但没有什么比一遍又一遍地查找“ERRORS”元素。
    • 谢谢!在 PageID 元素而不是序列号上,我需要集合中的位置。这可能吗?
    • @Jon:let 语句上的 sequenceNumber 的计算方式与您的示例相同。但是 .Select 有一个重载,允许您获取项目在 IEnumerable 集中的位置。我将在我的答案中添加一个示例。
    • @Jon:我更新了我的示例以包括使用 IEnumerable 集合中的位置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-07
    • 1970-01-01
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 2013-07-25
    • 1970-01-01
    相关资源
    最近更新 更多