web-dev-qa-db-fra.com

c # Méthode la plus rapide pour comparer des chaînes

J'ai remarqué que

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

sur les grandes cordes est légèrement plus rapide que juste

string1 == string2

Est-ce vrai? Et est-ce une bonne pratique pour comparer de grandes longueurs de chaîne avant de comparer des chaînes réelles?

22
CoolCodeBro

strings opérateur égal vérifie la longueur avant de comparer les caractères. Donc, vous ne sauvegardez pas la comparaison du contenu avec cette astuce. Vous peut-être enregistrez toujours quelques cycles de processeur car votre vérification de longueur suppose que les chaînes ne sont pas nulles, tandis que la BCL doit le vérifier. Donc, si les longueurs ne sont pas égales la plupart du temps, vous court-circuiterez quelques instructions.

Mais je me trompe peut-être ici. Peut-être que l'opérateur est aligné et les contrôles optimisés. Qui sait avec certitude? (Celui qui mesure.)

Si vous vous souciez de sauvegarder chaque cycle, vous pouvez probablement utiliser une stratégie différente en premier lieu. Peut-être que le code managé n'est même pas le bon choix. Compte tenu de cela, je recommande d'utiliser le formulaire plus court et de ne pas utiliser la vérification supplémentaire.

22
usr

Opérateur String.Equality ou == appelle en interne string.Equals, utilisez donc string.Equals ou == fourni par le cadre. Il est déjà suffisamment optimisé.

Il compare d'abord les références, puis la longueur, puis les caractères réels.

Vous pouvez trouver le code source ici

Code: (Source: 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);
}

et

[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);
    }
}
16
Habib

Mes résultats de test

comparer 10000 chaînes à 10000 autres chaînes de même longueur (256)

Temps (s1 == s2): 32536889 ticks

Heure (s1.Length == s2.Length) && (s1 == s2): 37380529 ticks

Comparez 10000 chaînes à 10000 autres chaînes Longueur aléatoire max 256

Temps (s1 == s2): 27223517 ticks

Heure (s1.Length == s2.Length) && (s1 == s2): 23419529 ticks

Comparez 10000 chaînes à 10000 autres chaînes Longueur aléatoire min 256 max 512

Temps (s1 == s2): 28904898 ticks

Heure (s1.Length == s2.Length) && (s1 == s2): 25442710 ticks

Ce que je trouve ahurissant, c'est qu'une comparaison de 10000 chaînes de longueur égale prendra plus de temps que la comparaison de la même quantité de données qui est plus grande.

Tous ces tests ont été effectués avec exactement les mêmes données.

7
user2888973

Selon ILSpy, la chaîne == L'opérateur est défini comme:

public static bool operator ==(string a, string b)
{
    return string.Equals(a, b);
}

Qui est défini comme

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

Je suppose que d'abord a == b est en fait une vérification d'égalité de référence (ILSpy le rend simplement comme ==), sinon ce serait une méthode infiniment récursive.

Cela signifie que == vérifie déjà la longueur des chaînes avant de comparer réellement leurs caractères.

4
p.s.w.g

Dans les chaînes terminées, il est logique de commencer à comparer les caractères, car vous ne pouvez pas calculer la longueur des chaînes sans itérer tous les caractères de toute façon, et la comparaison est susceptible de se terminer tôt.

Avec les chaînes comptées en longueur, la comparaison de la longueur doit être effectuée en premier, si vous testez l'égalité en octets. Vous ne pouvez même pas commencer à accéder aux données de caractères sans récupérer la longueur, car l'une pourrait être de longueur nulle.

Si vous faites une comparaison relationnelle, le fait de savoir que les longueurs sont différentes ne vous dit pas si le résultat doit être positif ou négatif. Et dans une comparaison sensible à la culture, des chaînes égales n'impliquent pas des longueurs égales. Donc, pour les deux, il vous suffit de comparer les données.

Si operator==(string, string) délègue simplement à une comparaison relationnelle, vous ne vous attendriez pas à ce que cela compare les longueurs. Vérifier la longueur avant de faire la comparaison pourrait donc être un avantage. Mais il semble que le cadre commence par une vérification de la longueur.

3
Ben Voigt

Pour les geeks parmi nous, voici une page qui fait un excellent travail de benchmarking de nombreuses façons de comparer les chaînes .

En résumé, la méthode la plus rapide semble être le CompareOrdinal:

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

La deuxième meilleure façon semble être d'utiliser un dictionnaire ou un Hashset avec la "clé" comme chaîne que vous souhaitez comparer.

Fait pour une lecture intéressante.

2
Free Coder 24

Si vous vous attendez à ce que les chaînes soient différentes dans leurs longueurs la plupart du temps, vous pouvez comparer leurs longueurs ET puis comparer les chaînes elles-mêmes en utilisant string.Compare. J'ai obtenu une amélioration des performances de près de 50% en procédant ainsi:

if (str1.Length == str2.Length)
{
    if (string.Compare(str1, str2, StringComparison.Ordinal) == 0)
    {
       doSomething()
    }
}

Dans ce cas, je m'attends à ce que les cordes soient différentes presque tout le temps, je pense que str1.Lenght est beaucoup moins cher que de comparer les cordes réelles. S'ils sont de taille égale, je les compare.

MODIFIER : Oubliez ce que j'ai dit. Utilisez simplement == et soyez heureux.

0
Ricardo Pieper

Je dirais que le premier est plus rapide est le résultat de string1.Length == string2.Length c'est faux. Grâce à l'évaluation en court-circuit (SCE), la comparaison réelle entre les chaînes n'est alors pas effectuée, ce qui pourrait vous faire gagner du temps.

Si les cordes sont égales cependant, la première est plus lente car elle vérifiera la longueur en premier et fera ensuite la même chose que la seconde.

Voir http://msdn.Microsoft.com/en-us/library/2a723cdk.aspx pour plus d'informations sur le && opérateur et SCE.

0
JLe

Donc, comme je l'ai promis, j'ai écrit un code court avec un chronomètre - vous pouvez copier le coller et essayer différentes chaînes et voir les différences

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;
    }
}

Mes conclusions:

  1. En vérifiant certaines chaînes (courtes et longues), j'ai vu que tous les résultats étaient presque les mêmes. Donc le premier si (avec le contrôle de longueur) est plus lent en 2/3.
  2. Et vous avez une méthode Equals dans la classe Object, utilisez-la simplement :)
  3. Vous pouvez l'essayer et nous donner également les résultats :)
0
Misha Zaslavsky