C#中比较字符串的最快方法

26

我注意到了

string1.Length == string2.Length && string1 == string2

在处理大字符串时,比仅仅使用常规方法略微更快。

string1 == string2

这是真的吗?在比较实际字符串之前比较大字符串长度是否是一种好的做法?


2
你是怎么“注意到”这个的?有什么事实可以支持这个注意到吗?有任何样本测试可以证明它吗? - Darin Dimitrov
3
测试非常困难,如果您所说的“测试”也包括“生成有意义的测试输入”的话。 - Sergey Kalinichenko
9
你怎么知道 string1 == string2 没有先检查长度呢? - Conrad Frix
2
除非您有一个基准测试,表明其中一个在处理特定数据时比另一个更快,否则正确的答案是“使用更易读的那个”。 - Ken White
2
@ConradFrix 它确实可以 ;) - Ralf
显示剩余5条评论
9个回答

25

string 的操作符“等于”会在比较字符之前进行长度检查。因此,使用此技巧并不能节省比较内容的操作。你可能仍然可以节省一些 CPU 周期,因为你的长度检查假设字符串不为 null,而 BCL 必须进行检查。所以,如果大多数情况下长度不相等,你将短路一些指令。

不过这里可能有误。也许操作符被内联并进行了优化。只有测量才能确定。(量化即真相)

如果你关心每一个循环周期,你应该考虑在第一次选择中使用不同的策略。也许受控代码甚至不是正确的选择。鉴于此,我建议使用较短的形式,并且不使用额外的检查。


4
如果长度大多数情况下不相等,那么会有几条指令被短路。现在这是一个统计学问题。 - jamesSampica
是的,这很可能就是为什么我注意到在循环一百万次时有非常轻微的性能差异,因为它跳过了两个值的空值检查,节省了几个周期。这种差异太微不足道,不需要再加一行代码。 - CoolCodeBro
根据参考来源,string 操作符重载 == 调用 String.Equals(),这只会引发更多关于表现差异的疑问。 - Softerware
当使用==比较字符串长度时,您也可以避免方法调用的开销。 - NetMage

19

字符串等式运算符 == 内部调用 string.Equals ,因此请使用框架提供的string.Equals == 。它已经足够优化。

它首先比较引用,然后比较长度和实际字符。

您可以在这里找到源代码。

代码:(源码:http://www.dotnetframework.org/default.aspx/4@0/4@0/DEVDIV_TFS/Dev10/Releases/RTMRel/ndp/clr/src/BCL/System/String@cs/1305376/String@cs

// Determines whether two strings match.
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(Object obj) {
    if (this == null)                        //this is necessary to guard against reverse-pinvokes and
        throw new NullReferenceException();  //other callers who do not use the callvirt instruction

    String str = obj as String;
    if (str == null)
        return false;

    if (Object.ReferenceEquals(this, obj))
        return true;

    return EqualsHelper(this, str);
}

[System.Security.SecuritySafeCritical]  // auto-generated
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
private unsafe static bool EqualsHelper(String strA, String strB)
{
    Contract.Requires(strA != null);
    Contract.Requires(strB != null);
    int length = strA.Length;
    if (length != strB.Length) return false;

    fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar)
    {
        char* a = ap;
        char* b = bp;

        // unroll the loop
#if AMD64
        // for AMD64 bit platform we unroll by 12 and
        // check 3 qword at a time. This is less code
        // than the 32 bit case and is shorter
        // pathlength

        while (length >= 12)
        {
            if (*(long*)a     != *(long*)b) break;
            if (*(long*)(a+4) != *(long*)(b+4)) break;
            if (*(long*)(a+8) != *(long*)(b+8)) break;
            a += 12; b += 12; length -= 12;
        }
 #else
        while (length >= 10)
        {
            if (*(int*)a != *(int*)b) break;
            if (*(int*)(a+2) != *(int*)(b+2)) break;
            if (*(int*)(a+4) != *(int*)(b+4)) break;
            if (*(int*)(a+6) != *(int*)(b+6)) break;
            if (*(int*)(a+8) != *(int*)(b+8)) break;
            a += 10; b += 10; length -= 10;
        }
  #endif

        // This depends on the fact that the String objects are
        // always zero terminated and that the terminating zero is not included
        // in the length. For odd string sizes, the last compare will include
        // the zero terminator.
        while (length > 0)
        {
            if (*(int*)a != *(int*)b) break;
            a += 2; b += 2; length -= 2;
        }

        return (length <= 0);
    }
}

它只比较引用,除非你传递特殊选项。无论如何,这是由于字符串intern池。 - It'sNotALie.
1
+1: ==操作符使用int i = strA.Length; if (i!= strB.Length) {return false;} - Tim Schmelter
2
为什么会有踩票?向下滚动到EqualsHelpers,看看实际发生了什么! - Sergey Kalinichenko
5
不,==操作符会比较对象的引用和值。C#与Java不同。 - jamesSampica
顺便说一句,发布那段代码可能会侵犯版权。我一直很小心,不注册查看共享源代码,即使它对我是可用的,因为我不想有任何创作衍生物的问题。 - Ben Voigt
显示剩余4条评论

10

对于我们中的极客,这里有一个页面可以很好地对比多种字符串比较方法

简而言之,最快的方法似乎是CompareOrdinal:

if (string.CompareOrdinal(stringsWeWantToSeeIfMatches[x], stringsWeAreComparingAgainst[x]) == 0)
{
//they're equal
}

第二种最好的方法似乎是使用字典或哈希集,将“key”作为要比较的字符串。
阅读起来很有趣。

1
链接似乎已经失效了,现在它指向当前页面。 - Zev Spitz
谢谢。我已经修复了链接。 - Free Coder 24
1
这些信息有多久了?对于DotNet Core还适用吗? - user1034912
@user1034912,是的!你可以自己试试!我刚刚试过了 :) - nkalfov
@user1034912,是的!你可以自己试试!我刚刚试过了 :) - undefined

7

我的测试结果

将10000个长度相同的字符串(长度为256)与10000个其他字符串进行比较

时间(s1 == s2):32536889个滴答声

时间(s1.Length == s2.Length)&&(s1 == s2):37380529个滴答声

将10000个字符串与10000个其他随机长度最大为256的字符串进行比较

时间(s1 == s2):27223517个滴答声

时间(s1.Length == s2.Length)&&(s1 == s2):23419529个滴答声

将10000个字符串与10000个其他字符串的随机长度在256到512之间进行比较

时间(s1 == s2):28904898个滴答声

时间(s1.Length == s2.Length)&&(s1 == s2):25442710个滴答声

令人难以置信的是,比较10000个等长字符串所需的时间比比较同样数量的更长数据还要长。

所有这些测试都是使用完全相同的数据进行的。


1
我有点晚了,但等长的字符串需要更长时间的原因是因为 == 运算符首先检查它们是否为空,然后检查它们是否具有相同的长度,最后再检查内容是否相同。如果长度相同,则继续执行;如果不同,则停止运算。 - soxroxr

4
根据ILSpy显示,字符串==运算符定义如下:
public static bool operator ==(string a, string b)
{
    return string.Equals(a, b);
}

这被定义为

public static bool Equals(string a, string b)
{
    return a == b || (a != null && b != null && a.Length == b.Length && string.EqualsHelper(a, b));
}

我假设首先a == b实际上是一个引用相等性检查(ILSpy只是将其渲染为==),否则这将是一个无限递归的方法。

这意味着,在实际比较字符之前,==已经检查了字符串的长度。


1
这是 ILSPy 对我(.NET 4, 长度检查在 EqualsHelper)的输出结果: public static bool Equals(string a, string b) { return a == b || (a != null && b != null && string.EqualsHelper(a, b)); } - Tim Schmelter
@TimSchmelter 你看的是哪个版本的程序集?发布的代码来自4.0.0.0吗?在2.0.0.0中,我看到return (value != null || this == null) && string.EqualsHelper(this, value); - p.s.w.g
@TimSchmelter 这确实很奇怪。我可以清楚地看到IL中对System.String::get_Length()的调用。是的,我在EqualsHelper内部还看到了另一个长度检查。 - p.s.w.g

3
在终止的字符串中,直接开始比较字符是有意义的,因为无论如何,没有迭代所有字符就不能计算字符串长度,并且比较很可能会提前退出。
使用计数长度的字符串时,如果您正在测试按字节相等,则应首先比较长度。您甚至不能开始访问字符数据而不检索长度,因为其中一个可能为空长度。
如果要进行关系比较,则知道长度不同并不能告诉您结果应该是正数还是负数。在基于文化背景的比较中,相等的字符串并不意味着长度相等。对于这两种情况,您需要仅比较数据。
如果operator==(string,string)只是委托给关系比较,您不希望比较长度。因此,在进行比较之前检查长度可能会有益。但似乎框架确实从长度检查开始。

0

正如我承诺的那样,我写了一个带有秒表的简短代码 - 你可以复制粘贴它并在不同的字符串上尝试,看看差异

class Program
{
    static void Main(string[] args)
    {
        string str1 = "put the first value";
        string str2 = "put the second value";
        CompareTwoStringsWithStopWatch(str1, str2); //Print the results.
    }

    private static void CompareTwoStringsWithStopWatch(string str1, string str2)
    {
        Stopwatch stopwatch = new Stopwatch();

        stopwatch.Start();
        for (int i = 0; i < 99999999; i++)
        {
            if (str1.Length == str2.Length && str1 == str2)
            {
                SomeOperation();
            }
        }
        stopwatch.Stop();

        Console.WriteLine("{0}. Time: {1}", "Result for: str1.Length == str2.Length && str1 == str2", stopwatch.Elapsed);
        stopwatch.Reset();

        stopwatch.Start();
        for (int i = 0; i < 99999999; i++)
        {
            if (str1 == str2)
            {
                SomeOperation();
            }
        }
        stopwatch.Stop();

        Console.WriteLine("{0}. Time: {1}", "Result for: str1 == str2", stopwatch.Elapsed);
    }

    private static int SomeOperation()
    {
        var value = 500;
        value += 5;

        return value - 300;
    }
}

我的结论:

  1. 我检查了一些字符串(短的和长的),发现所有结果几乎相同。所以第一个if语句(长度检查)比较慢,慢了2/3。
  2. 而且Object类中有一个Equals方法,直接使用即可 :)
  3. 你可以尝试一下并告诉我们结果 :)

0

我认为第一个更快的结果是由于string1.Length == string2.Length为false。由于短路评估(SCE),实际字符串比较未进行,这可能会节省时间。

然而,如果字符串相等,则第一个更慢,因为它将首先检查长度,然后执行与第二个相同的操作。

有关&&运算符和SCE的信息,请参见http://msdn.microsoft.com/en-us/library/2a723cdk.aspx


-1
如果你预计大多数情况下字符串长度不同,那么你可以通过比较它们的长度并且使用string.Compare来比较字符串本身。我通过这样做几乎获得了50%的性能提升。
if (str1.Length == str2.Length)
{
    if (string.Compare(str1, str2, StringComparison.Ordinal) == 0)
    {
       doSomething()
    }
}

在这种情况下,我预计字符串几乎每次都不同,我认为str1.Length比比较实际字符串要便宜得多。如果它们的大小相等,我会进行比较。
编辑:忘记我说的话。只需使用“==”并感到高兴。

1
字符串的 == 运算符也会先比较字符串长度。 - Tim Schmelter
所以我必须检查为什么我获得了这种性能改进。也许是其他原因。 - Ricardo Pieper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接