opublikował: mariusz, 2013-12-29

C# złączenia

Łączenie stringów

 

                Jaki jest najszybszy sposób łączenia stringów? Odpowiem na to pytanie w swojej pracy. Wybrałem ten problem ponieważ często na rozmowach rekrutacyjnych mamy za zadanie napisać metodę która będzie łączyła stringi w najszybszy sposób. Więc taka wiedza może się okazać bardzo przydatna.

Sam dostałem takie zadanie, oto moja metoda którą wtedy napisałem:

         static string CombineString(ref string argFirst, ref string argSecond)

         {

            return argFirst + argSecond;

         }

               Pomyślałem, że zwykłe połączenie stringów operatorem dodawania byłoby zbyt proste więc musiałem wymyślić coś lepszego. Wpadłem na pomysł aby argumenty do funkcji przekazać przez referencje więc ta metoda nie będzie musiała ich kopiować. Co jak się później okazało jest stekiem bzdur, ponieważ string jest typem referencyjnym i przekazanie go przez referencję nie robi różnicy. Rekruter powiedział, że najszybszym sposobem jest metoda String.Join.

               I tu moglibyście skończyć czytać tą pracę, ponieważ wyjawiłem najszybszy sposób, ale rekruter nie miał do końca racji. Jest jeszcze jeden sposób na najszybsze łączenie stringów. Każdy z tych sposobów radzi sobie ze swoim zadaniem lepiej w innej sytuacji. Drugim sposobem jest użycie metody z klasy StringBuilder a konkretnie metody Append.

               Metoda String.Join działa szybciej od metody StringBuilder.Append w sytuacji gdy znamy stringa, z kolei metoda StringBuilder.Append działa szybciej gdy string jest nieznany. Więc aby udowodnić wyższość jednej metody nad drugą a potem odwrotnie, będę potrzebował metody zwracającej losowy ciąg znaków:

 

         public static string GetRandomString()
        {
            return Path.GetRandomFileName();
        }
 
Następnie napiszmy metody łączące stringi, w sytuacji kiedy string jest znany. Dla uproszczenia przyjmą one jako argument tablice stringów:
 
         static string CombineByJoin(string[] arr)
        {
            return string.Join("", arr);
        }
          
         static string CombineByAppend(string[] arr)
        {
            StringBuilder toReturn = new StringBuilder();
            foreach (string s in arr)
            {
                toReturn.Append(s).Append("");
            }
            return toReturn.ToString();
        }
 
Kolejne metody będą łączyły stringi które będą na bieżąco generowane w metodzie:
 
         static string CombineByJoinUnknown()
        {
            return string.Join("", GetRandomString());
        }
 
         static string CombineByAppendUnknown()
        {
            StringBuilder toReturn = new StringBuilder();
            toReturn.Append(GetRandomString());
            return toReturn.ToString();
        } 
Mamy już wszystkie metody przygotowane, pozostaje tylko udowodnić, że String.Join działa szybciej gdy string jest znany, a StringBuilder.Append odwrotnie. Aby to zrobić potrzebna będzie nam tablica stringów ponieważ pierwsze dwie metody ją przyjmują jako argument, oto ona:         
         string[] arr = { "raz", "dwa", "trzy", "cztery", "pięć" }; 
Wszystko gotowe pozostaje tylko określić jakąś dużą liczbę wywołań tych metod i sprawdzić czas w jakim one połączą stringi, sprawdzimy wszystkie metody w trzech próbach przy tysiącu, dziesięciu tysiącach i stu tysiącach połączeń następnie wyniki przedstawię w milisekundach. Liczbę wywołań będzie przechowywała zmienna int _max. Zaczynajmy.
 
             private static int _max = 1000;
         
             var s1 = Stopwatch.StartNew();

            for (int i = 0; i < _max; i++)

            {

                CombineByJoin(arr);

            }

            s1.Stop();

 

            var s2 = Stopwatch.StartNew();

            for (int i = 0; i < _max; i++)

            {

                CombineByAppend(arr);

            }

            s2.Stop();

        

 

             var s3 = Stopwatch.StartNew();

            for (int i = 0; i < _max; i++)

            {

                CombineByJoinUnknown();

            }

            s3.Stop();

 

            var s4 = Stopwatch.StartNew();

            for (int i = 0; i < _max; i++)

            {

                CombineByAppendUnknown();

            }

            s4.Stop();

    
Console.WriteLine(s1.Elapsed.TotalMilliseconds.ToString("0.000 ms 
CombineByJoin"));
Console.WriteLine(s2.Elapsed.TotalMilliseconds.ToString("0.000 ms 
CombineByAppend"));
Console.WriteLine(s3.Elapsed.TotalMilliseconds.ToString("0.000 ms CombineByJoinUnknown"));
Console.WriteLine(s4.Elapsed.TotalMilliseconds.ToString("0.000 ms CombineByAppendUnknown"));
 
Przy tysiącu próbach, czterech uruchomieniach programu wyniki były następujące:
0,391 ms CombineByJoin
0,598 ms CombineByAppend
1,873 ms CombineByJoinUnknown
1,476 ms CombineByAppendUnknown
----------------------------------
0,373 ms CombineByJoin
0,591 ms CombineByAppend
1,950 ms CombineByJoinUnknown
1,528 ms CombineByAppendUnknown
-----------------------------------
0,467 ms CombineByJoin
0,844 ms CombineByAppend
2,905 ms CombineByJoinUnknown
2,504 ms CombineByAppendUnknown
-----------------------------------
0,373 ms CombineByJoin
0,586 ms CombineByAppend
1,868 ms CombineByJoinUnknown
1,466 ms CombineByAppendUnknown
 Przy dziesięciu tysiącach prób:
2,570 ms CombineByJoin
5,141 ms CombineByAppend
20,132 ms CombineByJoinUnknown
17,841 ms CombineByAppendUnknown
----------------------------------
1,934 ms CombineByJoin
4,818 ms CombineByAppend
20,997 ms CombineByJoinUnknown
17,377 ms CombineByAppendUnknown
-----------------------------------
1,941 ms CombineByJoin
4,293 ms CombineByAppend
18,688 ms CombineByJoinUnknown
17,501 ms CombineByAppendUnknown
-----------------------------------
2,009 ms CombineByJoin
7,730 ms CombineByAppend
25,102 ms CombineByJoinUnknown
20,335 ms CombineByAppendUnknown

 

 
Przy stu tysiącach prób:
19,886 ms CombineByJoin
45,239 ms CombineByAppend
190,481 ms CombineByJoinUnknown
140,439 ms CombineByAppendUnknown
-----------------------------------
18,625 ms CombineByJoin
44,995 ms CombineByAppend
132,673 ms CombineByJoinUnknown
116,690 ms CombineByAppendUnknown
------------------------------------
24,770 ms CombineByJoin
49,116 ms CombineByAppend
136,068 ms CombineByJoinUnknown
120,792 ms CombineByAppendUnknown
------------------------------------
 20,365 ms CombineByJoin
 45,075 ms CombineByAppend
132,037 ms CombineByJoinUnknown
124,641 ms CombineByAppendUnknown

 

 
Jak widać wszystkie 12 prób udowadniają to co wcześniej napisałem, String.Join szybciej łączy stringi jeżeli są wcześniej znane. Jest to przydatna informacja, jednak zakończę tą pracę radą aby nie używać tych metod, w sytuacjach gdy musimy połączyć stringi np. jednorazowo wtedy nad niezauważalną poprawę wydajności należy nadłożyć lepszą czytelność kodu którą nam zapewnia operator dodawania.
Zaloguj się aby dodać komentarz
« starsze wpisy