56 votes

Quel est le moyen le plus rapide de parcourir différents caractères dans une chaîne en C #?

Le titre est la question. Ci-dessous est ma tentative de réponse à travers la recherche. Mais je n'ai pas confiance en mon mal informés de recherche j'ai donc toujours se poser la question (Ce qui est le moyen le plus rapide pour parcourir les différents caractères dans une chaîne de caractères en C#?).

Parfois je veux faire défiler les caractères d'une chaîne de caractères un par un, comme lors de l'analyse de imbriquée jetons -- quelque chose qui ne peut être fait avec des expressions régulières. Je me demande ce que le moyen le plus rapide est de parcourir les différents caractères dans une chaîne, en particulier les très grandes chaînes.

J'ai fait un tas de tests moi-même et mes résultats sont ci-dessous. Cependant, il existe de nombreux lecteurs avec beaucoup plus de connaissance de la profondeur de l' .NET CLR et le compilateur C# donc je ne sais pas si je suis absent quelque chose d'évident, ou si j'ai fait une erreur dans mon code de test. Donc je sollicite votre réponse collective. Si quelqu'un a un aperçu de la façon dont la chaîne d'indexation fonctionne réellement, ce serait très utile. (Est-ce un C# langage compilé dans quelque chose d'autre derrière les coulisses? Ou quelque chose de construit dans le CLR?).

La première méthode à l'aide d'un flux a été prise directement à partir de la accepté de répondre à partir de la discussion: comment générer un flux à partir d'une chaîne de caractères?

Tests

longString est un 99,1 millions de dollars chaîne de caractères composée de 89 copies de la plaine-version texte de la spécification du langage C#. Les résultats indiqués sont pour les 20 itérations. Où il y a un "démarrage", le temps (comme pour la première itération de la créé implicitement tableau dans la méthode n ° 3), j'ai testé séparément, comme par la rupture de la boucle après la première itération.

Résultats

De mes tests, la mise en cache à la chaîne dans un char tableau à l'aide de la ToCharArray() est la méthode la plus rapide pour parcourir l'ensemble de la chaîne. Le ToCharArray() la méthode est de charge initiale, et l'accès ultérieur à des caractères individuels est légèrement plus rapide que par le haut dans l'index de l'accesseur.

                                           milliseconds
                                ---------------------------------
 Method                         Startup  Iteration  Total  StdDev
------------------------------  -------  ---------  -----  ------
 1 index accessor                     0        602    602       3
 2 explicit convert ToCharArray     165        410    582       3
 3 foreach (c in string.ToCharArray)168        455    623       3
 4 StringReader                       0       1150   1150      25
 5 StreamWriter => Stream           405       1940   2345      20
 6 GetBytes() => StreamReader       385       2065   2450      35
 7 GetBytes() => BinaryReader       385       5465   5850      80
 8 foreach (c in string)              0        960    960       4

Mise à jour: Par @Eric commentaire, voici les résultats pour 100 itérations sur une plus normal 1,1 M de chaîne de char (une copie de la C# spec). L'indexeur et des tableaux de char sont encore plus rapide, suivie par foreach(char en string), suivis par les flux de méthodes.

                                           milliseconds
                                ---------------------------------
 Method                         Startup  Iteration  Total  StdDev
------------------------------  -------  ---------  -----  ------
 1 index accessor                     0        6.6    6.6    0.11
 2 explicit convert ToCharArray     2.4        5.0    7.4    0.30
 3 for(c in string.ToCharArray)     2.4        4.7    7.1    0.33
 4 StringReader                       0       14.0   14.0    1.21
 5 StreamWriter => Stream           5.3       21.8   27.1    0.46
 6 GetBytes() => StreamReader       4.4       23.6   28.0    0.65
 7 GetBytes() => BinaryReader       5.0       61.8   66.8    0.79
 8 foreach (c in string)              0       10.3   10.3    0.11     

Code Utilisé (testé séparément; représenté pour des raisons de concision)

//1 index accessor
int strLength = longString.Length;
for (int i = 0; i < strLength; i++) { c = longString[i]; }

//2 explicit convert ToCharArray
int strLength = longString.Length;
char[] charArray = longString.ToCharArray();
for (int i = 0; i < strLength; i++) { c = charArray[i]; }

//3 for(c in string.ToCharArray)
foreach (char c in longString.ToCharArray()) { } 

//4 use StringReader
int strLength = longString.Length;
StringReader sr = new StringReader(longString);
for (int i = 0; i < strLength; i++) { c = Convert.ToChar(sr.Read()); }

//5 StreamWriter => StreamReader 
int strLength = longString.Length;
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(longString);
writer.Flush();
stream.Position = 0;
StreamReader str = new StreamReader(stream);
while (stream.Position < strLength) { c = Convert.ToChar(str.Read()); } 

//6 GetBytes() => StreamReader
int strLength = longString.Length;
MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(longString));
StreamReader str = new StreamReader(stream);
while (stream.Position < strLength) { c = Convert.ToChar(str.Read()); }

//7 GetBytes() => BinaryReader 
int strLength = longString.Length;
MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(longString));
BinaryReader br = new BinaryReader(stream, Encoding.Unicode);
while (stream.Position < strLength) { c = br.ReadChar(); }

//8 foreach (c in string)
foreach (char c in longString) { } 

Accepté de répondre:

J'ai interprété @CodeInChaos et Ben notes comme suit:

fixed (char* pString = longString) {
    char* pChar = pString;
    for (int i = 0; i < strLength; i++) {
        c = *pChar ;
        pChar++;
    }
}

Exécution pour 100 itérations au cours de la courte chaîne était de 4,4 ms, avec < 0,1 ms st dev.

28voto

Jon Skeet Points 692016

Une raison de ne pas inclure foreach ?

 foreach (char c in text)
{
    ...
}
 

Soit dit en passant, cela sera-t-il vraiment votre goulot d'étranglement? Quelle proportion de votre temps total d'exécution l'itération elle-même prend-elle?

9voto

Hans Passant Points 475940

Ces artificiels tests sont très dangereux. Remarquable, c'est que votre //2 et //les 3 versions du code n'a jamais vraiment indices de la chaîne. La gigue optimiseur juste jette le code depuis la variable c n'est pas du tout. Vous êtes simplement en mesure de combien de temps de la boucle for() prend. Vous ne pouvez pas vraiment voir ce que si vous regardez le code machine généré.

Changer en c += longString[i]; de la force de la matrice de l'indexeur pour être utilisé.

Ce qui est absurde, bien sûr. Le profil réel de code.

8voto

Ben Voigt Points 151460

La réponse est plus rapide d'utiliser le C++/CLI: Comment: Accéder aux Caractères d'un Système::String

Cette approche parcourt les personnages en place dans la chaîne à l'aide de l'arithmétique des pointeurs. Il n'y a pas de copies, pas implicite gamme de contrôles, et non par des éléments des appels de fonction.

Il est probablement possible d'obtenir (presque, C++/CLI ne nécessite pas de brochage) la même performance à partir de C# par l'écriture d'un dangereux version C# de PtrToStringChars.

Quelque chose comme:

unsafe char* PtrToStringContent(string s, out GCHandle pin)
{
    pin = GCHandle.Alloc(s, GCHandleType.Pinned);
    return (char*)pin.AddrOfPinnedObject().Add(System.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringData).ToPointer();
}

N'oubliez pas d'appeler GCHandle.Freepar la suite.

CodeInChaos commentaire souligne que le C# fournit un sucre syntaxique pour cela:

fixed(char* pch = s) { ... }

4voto

L.B Points 54001

Si l'optimisation micro est très importante pour vous, essayez ceci. (J'ai supposé que la longueur de la chaîne d'entrée était multiple de 8 pour des raisons de simplicité)

 unsafe void LoopString()
{
    fixed (char* p = longString)
    {
        char c1,c2,c3,c4;
        Int64 len = longString.Length;
        Int64* lptr = (Int64*)p;
        Int64 l;
        for (int i = 0; i < len; i+=8)
        {
            l = *lptr;
            c1 = (char)(l & 0xffff);
            c2 = (char)(l >> 16);
            c3 = (char)(l >> 32);
            c4 = (char)(l >> 48);
            lptr++;
        }
    }
}
 

Je plaisante, n'utilisez jamais ce code :)

3voto

Si la vitesse compte vraiment, for est plus rapide que foreach

 for (int i = 0; i < text.Length; i++) {
   char ch = text[i];
   ...
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X