LINQ性能影响

时间:2009-01-18 09:49:10

标签: c# linq

我有一个简单的过程来删除XML中非法的所有字符串:

string SanitizeXml(string xml)
{
    return string.Concat
        (xml.ToCharArray().Where(c => IsLegalXmlChar(c)).ToArray());
}

这很好,很简洁。但我很担心它的表现。使用简单的for循环可以轻松完成同样的事情:

string SanitizeXml(string xml)
{
    var buffer = new StringBuilder();

    foreach(char c in xml)
    {
        if (IsLegalXmlChar(c))
        {
            buffer.Append(c);
        }
    }

    return buffer.ToString();
}

在我看来,在第二个例子中,xml被转换为char [],而Where()的IEnumerable< char>回到char []。我似乎用LINQ做了很多 - 在数组和枚举之间进行更改。

我应该关注这件事吗?一般来说,当有一个明确的替代方案可能更加冗长时,我会依赖LINQ扩展方法获得什么样的性能。

也许这是一个过于宽泛的问题。

4 个答案:

答案 0 :(得分:6)

好吧,你不需要第一次调用ToCharArray()来开始 - 字符串实现IEnumerable<char>。但是,我同意在这种情况下StringBuilder和循环可能更合适。

我不确定string.Concat(char [])是做什么的,btw - 为什么你不只是使用带有char数组的字符串构造函数?换句话说,经过这些修改后:

static string SanitizeXml(string xml)
{
    return new string (xml.Where(c => IsLegalXmlChar(c)).ToArray());
}

我仍然更喜欢StringBuilder解决方案,但可以通过提供适当的容量来改善常见情况(非常少的非法字符):

string SanitizeXml(string xml)
{
    var buffer = new StringBuilder(xml.Length);

    foreach(char c in xml)
    {
        if (IsLegalXmlChar(c))
        {
                buffer.Append(c);
        }
    }

    return buffer.ToString();
}

我以前没想过的一个替代方案可能是StringBuilder上的扩展方法:

// Can't just call it Append as otherwise StringBuilder.Append(object) would
// be used :(
public static StringBuilder AppendSequence(this StringBuilder builder,
                                           IEnumerable<char> sequence)
{
    foreach (char c in sequence)
    {
        builder.Append(c);
    }
    return builder;
}

然后你可以像这样使用它:

xml = new StringBuilder(xml.Length)
            .AppendSequence(xml.Where(IsLegalXmlChar)
            .ToString();

(如果你愿意的话,你可以让AppendSequence的其他重载来获取IEnumerable等。)

编辑:另一种选择可能是避免经常调用追加,而是使用the overload which appends a substring。然后你可以再次为StringBuilder构建一个扩展方法,类似于(完全没有测试,我很害怕 - 我甚至没有尝试过编译它):

public static StringBuilder AppendWhere(this StringBuilder builder,
                                        string text,
                                        Func<char, bool> predicate)
{
    int start = 0;
    bool lastResult = false;
    for (int i=0; i < text.Length; i++)
    {
        if (predicate(text[i]))
        {
            if (!lastResult)
            {
                start = i;
                lastResult = true;
            }
        }
        else
        {
            if (lastResult)
            {
                builder.Append(text, start, i-start);
                lastResult = false;
            }
        }
    }
    if (lastResult)
    {
         builder.Append(text, start, text.Length-start);
    }
    return builder;
}

示例的用法:

xml = new StringBuilder(xml.Length).AppendWhere(xml, IsLegalXmlChar)
                                   .ToString();

另一个替代方法是将它更改为String上的扩展方法,懒惰地创建StringBuilder,如果你以start = 0结束,只需返回原始字符串。

答案 1 :(得分:3)

对于简单的foreach循环,这两个版本基本相同。

首先考虑一下为什么我们有IEnumerable类型,它与foreach循环一起使用!如果你使用foreach循环,那么你的字符串会在幕后转换为IEnumerable,因此逻辑基本上与LINQ版本相同。

除非你投入一些优化,即使用StringBuilder,性能不会太差异。

这是一个分析代码:http://pastebin.com/f125a9a46

来自@Chris,@ Marc_Garvell,@ Jon_Skeet

这是我机器上的结果:

Simple LINQ version                      : 43270ms
For-each-loop version w/ StringBuilder   : 35875ms
For-each-loop version w/ List            : 37595ms
For-index loop w/ StringBuilder          : 37589ms
Jon Skeet's AppendWhere version          : 28980ms

以下是启用代码优化的结果:

Simple LINQ version                      : 27814ms
For-each-loop version w/ StringBuilder   : 23453ms
For-each-loop version w/ List            : 21374ms
For-index loop w/ StringBuilder          : 22308ms
Jon Skeet's AppendWhere version          : 10884ms

LINQ和foreach循环之间的4.3秒差异并不能证明你已经使用了StringBuilder而LINQ有从char数组重建的开销这一事实,因此你无法证明400,000,000个字符是正确的。

答案 2 :(得分:0)

就个人而言,我不会在这种情况下使用LINQ /扩展方法; LINQ是一个功能强大的工具,但它不应该用于所有问题。

根据定义,使用Ienumerable<T>ToArray等的现有LINQ扩展方法将为您的方案添加一些开销。问题是:它对您的方案有意义吗?例如,如果您要进行数据的网络传输,则此处几皮秒无关紧要。但是如果你在一个紧凑的循环中进行xml,它可能会。

更好的解决方法是直接使用框架代码对其进行编码...我会看看是否可以找到最简单的选项......

注意:此处的另一个微优化是使用现有字符串的长度预先初始化StringBuilder

答案 3 :(得分:-1)

我会使用正则表达式来清理字符串。它看起来比正在讨论的选项更清晰。有人可以使用正则表达式进行性能测试,如果我做错了,请告诉我吗?

谢谢!