【问题标题】:Can someone help me improve this linq有人可以帮我改进这个 linq
【发布时间】:2011-10-26 04:11:42
【问题描述】:

我这里有一个代码,它使用 linq 为工程师列表生成 XML。我的问题是有什么方法可以改进和加速这种方法

    public static string CreateXMLforEngineersByLinq(List<Engineers> lst)
    {
        string x = "<Engineers>\n";
        x += string.Concat(lst.Select(s =>
                string.Format("<Engineer>\n<LicenseID>{0}</LicenseID>\n<LastName>{1}</LastName>\n<FirstName>{2}</FirstName>\n<MiddleName>{3}</MiddleName>\n</Engineer>\n", 
                s.LicenseID, s.LastName, s.FirstName, s.MiddleName)));
        return x + "</Engineers>";
    }

结果:

嗨,下面的代码是为我添加的,以显示我产生更多答案的方法的实际速度,欢迎修改再次感谢那些在这里提供帮助的人:)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using test.Classes;

namespace test.LINQ
{
public static class BatchOperations
{
    delegate string Operations(List<Engineers> i);        
    public static List<int> BatchAddition(List<int> lstNumbers)
    {
        lstNumbers = (from nl in lstNumbers
                      select nl + 2).ToList();

        return lstNumbers;
    }

    public static string CreateXMLforEngineersTheSimpleWay(IEnumerable<Engineers> lst)
    {
        StringBuilder x = new StringBuilder();
        x.AppendLine("<Engineers>");
        foreach (var s in lst)
        {
            x.AppendFormat("<Engineer>\n<LicenseID>{0}</LicenseID>\n<LastName>{1}</LastName>\n<FirstName>{2}</FirstName>\n<MiddleName>{3}</MiddleName>\n</Engineer>\n",
                           s.LicenseID,
                           s.LastName,
                           s.FirstName,
                           s.MiddleName);
        }
        x.AppendLine("</Engineers1>");
        return x.ToString();
    }

    public static string CreateXMLforEngineersByLinq(List<Engineers> lst)
    {
        string x = "<Engineers>\n";
        x += string.Concat(lst.Select(s =>
                string.Format("<Engineer>\n<LicenseID>{0}</LicenseID>\n<LastName>{1}</LastName>\n<FirstName>{2}</FirstName>\n<MiddleName>{3}</MiddleName>\n</Engineer>\n", 
                s.LicenseID, s.LastName, s.FirstName, s.MiddleName)));
        return x + "</Engineers2>";
    }

    public static string CreateXMLforEngineersByLoop(List<Engineers> lst)
    {
        string XmlForEngineers = "<Engineers>";
        foreach (Engineers item in lst)
        {
            XmlForEngineers += string.Format("<Engineer>\n<LicenseID>{0}</LicenseID>\n<LastName>{1}</LastName>\n<FirstName>{2}</FirstName>\n<MiddleName>{3}</MiddleName>\n</Engineer>\n"
                , item.LicenseID, item.LastName, item.FirstName, item.MiddleName);
        }
        XmlForEngineers += "</Engineers3>";
        return XmlForEngineers;
    }

    public static void ShowEngineersByLicense()
    {
        List<Engineers> lstEngr = new List<Engineers>();

        Engineers tom = new Engineers();
        tom.FirstName = "Tom";
        tom.MiddleName = "Brook";
        tom.LastName = "Crook";
        tom.LicenseID = "1343-343434";

        Engineers ken = new Engineers();
        ken.FirstName = "ken";
        ken.MiddleName = "Brook";
        ken.LastName = "Crook";
        ken.LicenseID = "1343-343434";

        Engineers ben = new Engineers();
        ben.FirstName = "ben";
        ben.MiddleName = "Brook";
        ben.LastName = "Crook";
        ben.LicenseID = "1343-343434";

        for (int y = 0; y <= 1000; y++)
        {
            lstEngr.Add(tom);
            lstEngr.Add(ken);
            lstEngr.Add(ben);
        }

        List<Operations> j = new List<Operations>();
        j.Add(a => CreateXMLforEngineersTheSimpleWay(lstEngr));
        j.Add(i => CreateXMLforEngineersByLinq(lstEngr));
        j.Add(i => CreateXMLforEngineersByLoop(lstEngr));

        DateTime start, end;
        TimeSpan diff1 = new TimeSpan();

        foreach (Operations currentMethod in j)
        {
            start = DateTime.Now;
            Console.Write(currentMethod(lstEngr));
            end = DateTime.Now;
            diff1 = end - start;
            Console.WriteLine(diff1);
            Console.Write("\n\n");
        }
    }
}

}

【问题讨论】:

  • 您应该重新进行基准测试。 1:您需要列表中的 3 人以上才能产生显着差异(尝试 10,000 人)。 2:他们需要分配不同的字符串(这很可能是现实世界中的情况)。 C# 具有不可变的字符串,因此拥有相同的 4 个字符串会非常快。使用 Guid.NewGuid().ToString()。第三,您需要多次运行测试(例如 100 次),然后取平均值。我很难相信 string += string 的表现优于 StringBuilder
  • 5000人跑,我得到的结果是:LINQ: 0.019ms, For-Loop: 12.059 seconds, Simple way: 0.010ms
  • 我很欣赏@Rob 的评论
  • 我再次用 300k 记录对其进行了基准标记,但这次 linq 来得更快
  • @Allan:这是您应用程序的瓶颈吗?如果不是,那为什么还要优化它呢?只需尽可能编写最简单、最易于维护的代码,并且仅在必要时对其进行优化。如果您正在序列化,您通常会写入磁盘或网络,这几乎肯定会使您可以进行的任何内存优化相形见绌。话虽如此,如果这需要 12 秒,则可能发生了一些奇怪的事情,在这种情况下值得一看。

标签: c# linq


【解决方案1】:

使用XmlSerializer,为方便起见构建一个扩展方法。

public static class XmlExtensions
{
    public static string ToXml<T>(this T instance)
    {
        var xmlSerializer = new XmlSerializer(typeof(T));
        var stringWriter = new StringWriter();
        xmlSerializer.Serialize(stringWriter, instance);
        return stringWriter.ToString();
    }
}

public static string CreateXMLforEngineersByLinqMyWay(List<Engineers> lst)
{
    return string.Format("<Engineers>{0}</Engineers>"
        , string.Join("",
            lst.Select(s => s.ToXml()) // Might have to put `.ToArray()` here
            )
        );
}

或者你可以简单地这样做:

return lst.ToXml();

如果这是更大的对象树序列化的一部分,那么就放弃整个方法,并在顶级对象上执行ToXml

您可能需要编辑 ToXml 方法以使其删除 XML 命名空间等。

【讨论】:

  • 这比我预期的要慢一点:(
  • @Allan:第一次访问序列化时速度很慢,因为序列化程序集将在运行时编译。所有后续访问都会快得多。为了避免这种情况,您可以预编译序列化程序集。这是 Visual Studio 中的一个项目选项,或者您可以将 sgen.exe 添加到您的构建中。见:devolutions.net/articles/dot-net/…
  • @Allan:强制生成序列化程序集后,我得到了 100k 预先创建的 Engineer 实例的这些时间:ToXml:0.42-0.45s,CreateXmlforEngineersByLinqMyWay:2.5-2.8s(较低的值用于删除命名空间,ala this answer),CreateXmlforEngineersTheSimpleWay:0.25-0.3s,CreateXmlforEngineersByLinq:0.55-0.65s。似乎ToXml 可能会胜过 MyWay(我有点预料到这一点,因为循环必须两次构建字符串 - 一次在格式化程序中,一次在 string.Join 中)。
【解决方案2】:

最明显的加速:使用StringBuilder 而不是字符串。请参阅this article,全面讨论 StringBuilder 与简单连接相比的性能。

此外,在这种情况下,您可以通过完全放弃 linq 获得更简单、更快速的解决方案;

 public static string CreateXMLforEngineersTheSimpleWay(IEnumerable<Engineers> lst)
    {
        StringBuilder x = new StringBuilder();
        x.AppendLine("<Engineers>");
        foreach(var s in lst)
        {
              x.AppendFormat("<Engineer>\n<LicenseID>{0}</LicenseID>\n<LastName>{1}</LastName>\n<FirstName>{2}</FirstName>\n<MiddleName>{3}</MiddleName>\n</Engineer>\n", 
                             s.LicenseID, 
                             s.LastName, 
                             s.FirstName, 
                             s.MiddleName);
        }
        x.AppendLine("</Engineers>");
        return x.ToString();
    }

如果您对 Linq 有信心,可以使用 String.Join 方法。但是,我怀疑这不会像第一个解决方案那样好,因为下面的这个解决方案必须创建一个临时数组。

public static string CreateXMLforEngineersByLinq(List<Engineers> lst)
{
    var x = new StringBuilder();
    x.AppendLine("<Engineers>)";
    x.Append(
                string.Join(
                             "\n",
                             lst.Select(s =>
                                string.Format(
                                           "<Engineer>\n<LicenseID>{0}</LicenseID>\n<LastName>{1}</LastName>\n<FirstName>{2}</FirstName>\n<MiddleName>{3}</MiddleName>\n</Engineer>\n", 
                                            s.LicenseID, 
                                            s.LastName, 
                                            s.FirstName, 
                                            s.MiddleName
                                            )
                              ).ToArray()
                        );
      x.AppendLine("</Engineers>");
      return x.ToString();

}

【讨论】:

  • 为什么 StringBuilder 会比 string.Contact() 快?
  • String.Concat 在内部使用StringBuilder,因此除了第一个和最后一个附加以及一些IEnumerable 开销之外,它应该不会更快。
  • 嗨@Andrew Shepherd 我已经编辑了问题,看看那里的结果:)
【解决方案3】:

所有其他示例都使用字符串 concat 来生成您的 XML。这样做的缺点是,如果您的任何值没有正确转义 XML 中不受支持的字符(如

public static string CreateXMLforEngineersByLinq(List<Engineers> lst) 
{ 
    string x = New XElement("Engineers", 
                   lst.Select(new XElement, "Engineer",
                      new XElement("LicenseID", s.LicenseID),
                      new XElement("LastName", s.LastName),
                      new XElement("FirstName", s.FirstName),
                      new XElement("MiddleName", s.MiddleName)
                   )
               );
    return x.ToString();
} 

如果您不想在将整个 XML 推送到字符串之前在内存中创建整个 XML 的内存开销,您可能需要考虑使用 XStreamingElement (http://msdn.microsoft.com/en-us/library/system.xml.linq.xstreamingelement.aspx)。有关如何使用它的快速视频,请参阅 http://www.microsoft.com/uk/msdn/nuggets/nugget/295/LINQ-to-XML-Streaming-Large-Data-Files-Out-of-Memory.aspx

【讨论】:

    【解决方案4】:

    我认为值得测试一下使用XmlSeriaizer 对整个列表进行序列化是否更快。

    像这样:

    using (var fileStream = new FileStream("engineers.xml", FileMode.OpenOrCreate))
    {
         var xmlSerializer = new XmlSerializer(typeof(List<Engineer>))
         xmlSerializer.Serialize(fileStream, list);
    }
    

    【讨论】:

    • +1;在编辑我的几分钟后,请立即阅读您的答案。我很确定它会比 OP 更快。没有字符串格式...
    • 哎呀,谢谢我尝试使用它并比较结果:) 谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-13
    • 2015-05-31
    相关资源
    最近更新 更多