C# złączenia
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
Przy dziesięciu tysiącach prób:
2,570 ms CombineByJoin 5,141 ms CombineByAppend 20,132 ms CombineByJoinUnknown 17,841 ms CombineByAppendUnknown
Przy stu tysiącach prób:
19,886 ms CombineByJoin 45,239 ms CombineByAppend 190,481 ms CombineByJoinUnknown 140,439 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.