2011-01-21 8 views
6
A = string.Concat("abc","def") 

B = "abc" + "def" 

A względem Bdodanie ciągów w języku C#, jak robi to kompilator?

Ostatnio byłem zdezorientowany, dlaczego wielu byłoby powiedzieć, że na pewno ma dużo szybsze przetwarzanie w porównaniu do B. Ale chodzi o to, by po prostu powiedzieć, bo ktoś powiedział tak albo dlatego, że tak po prostu jest. Przypuszczam, że słyszę tutaj znacznie lepsze wyjaśnienia.

W jaki sposób kompilator traktuje te łańcuchy?

Dziękujemy!

+2

Z ciągami o tym rozmiarze, to nie ma znaczenia –

Odpowiedz

12

Pierwszą rzeczą, jaką zrobiłem, kiedy dołączyłem do zespołu kompilatorów C#, było przepisanie optymalizatora pod kątem konkatenacji ciągów. Dobre czasy.

Jak już wspomniano, konkatele łańcuchowe ciągów stałych są wykonywane podczas kompilacji. Dla stałych ciągów zrobić wymyślnej rzeczy:

a + b --> String.Concat(a, b) 
a + b + c --> String.Concat(a, b, c) 
a + b + c + d --> String.Concat(a, b, c, d) 
a + b + c + d + e --> String.Concat(new String[] { a, b, c, d, e }) 

Korzyści z tych optymalizacje to, że metoda String.Concat może patrzeć na wszystkich argumentów, ustalenia sumy ich długości, a następnie zrobić jeden wielki ciąg, który może trzymaj wszystkie wyniki.

Oto interesująca.Załóżmy, że masz metodę M, która zwraca ciąg znaków:

s = M() + ""; 

Jeśli M() zwróci wartość null, wynikiem będzie pusty ciąg znaków. (null + empty jest pusty.) Jeśli M nie zwróci wartości null, wynik pozostaje niezmieniony przez konkatenację pustego łańcucha. Dlatego jest to w rzeczywistości zoptymalizowane jako nie połączenie do String.Concat w ogóle! Stanie się to

s = M() ?? "" 

Schludnie, co?

5

W języku C# operatorem dodawania ciągów jest tylko cukier syntaktyczny dla String.Concat. Można to sprawdzić, otwierając zespół wyjściowy w odbłyśniku.

Inną rzeczą, na którą należy zwrócić uwagę, jest to, że jeśli w kodzie występują ciągi literowe (lub stałe), tak jak w przykładzie, kompilator zmienia to nawet na B = "abcdef".

Ale jeśli użyjesz String.Concat z dwoma literałami lub stałymi ciągami, String.Concat będzie nadal wywoływany, pomijając optymalizację, więc operacja + będzie faktycznie szybsza.

Tak więc, aby podsumować:

stringA + stringB staje String.Concat(stringA, stringB).
"abc" + "def" staje "abcdef "
String.Concat("abc", "def") pozostaje ten sam

coś innego po prostu musiałem spróbować:

w C++/CLI, "abc" + "def" + "ghi" jest rzeczywiście tłumaczone na String.Concat(String.Concat("abc", "def"), "ghi")

+3

Nie z dwoma literałami łańcuchowymi, nie ma: '' B' będzie ustawiony bezpośrednio na "abcdef". – LukeH

+0

już dodano, że zaraz po wysłaniu :) – Botz3000

1

Właściwie B został rozwiązany podczas czas kompilacji. Kończysz z B = "abcdef", podczas gdy dla A, konkatenacja jest odkładana do czasu wykonania.

+1

Aby dodać do tego, użycie '+' na łańcuchach, gdy * nie * skonfrontowane z literałami zostanie przekształcone w pojedyncze wywołanie 'string.Concat()' – Joey

5
+1

+1 dobry odczyt ! dzięki – naveen

+0

Tylko jedna uwaga: Myślę, że wielu ludzi zbyt mocno podkreśla StringBuilder. Istnieje również klasa StringWriter w .NET, która jest znacznie łatwiejsza w użyciu, ponieważ jej publiczny interfejs jest bardzo podobny do tego, co wszyscy wiedzą z klasy Console. –

+0

Zgodnie z MSDN, 'StringWriter' jest opakowaniem wokół' StringBuilder'. Dlatego nie jest istotne, aby wspomnieć o 'StringWriter' w kontekście optymalizacji kodu, jeśli' StringBuilder' został już omówiony. – Brian

1

Jeśli ciągi są literały, jak w swoim pytaniu, wtedy połączeniem strun przypisanych B zostaną wykonane w czasie kompilacji. Twój przykład przekłada się:

string a = string.Concat("abc", "def"); 
string b = "abcdef"; 

Jeśli struny nie są literały następnie kompilator przełoży operatora + w Concat rozmowy.

Więc to ...

string x = GetStringFromSomewhere(); 
string y = GetAnotherString(); 

string a = string.Concat(x, y); 
string b = x + y; 

... jest tłumaczona na to w czasie kompilacji:

string x = GetStringFromSomewhere(); 
string y = GetAnotherString(); 

string a = string.Concat(x, y); 
string b = string.Concat(x, y); 
1

W tym konkretnym przypadku, dwa są właściwie identyczne. Kompilator przekształci drugi wariant, ten przy użyciu operatora +, w wywołanie Concat, pierwszego wariantu.

Cóż, to znaczy, jeśli dwa faktycznie zawierały zmienne łańcuchowe, które zostały połączone.

ten kod:

B = "abc" + "def"; 

rzeczywiście przekształca się to bez konkatenacji w ogóle:

B = "abcdef"; 

Można to zrobić ponieważ wynik dodawania mogą być obliczane w czasie kompilacji, więc kompilator to robi.

Jednakże, jeśli było użyć czegoś takiego:

A = String.Concat(stringVariable1, stringVariable2); 
B = stringVariable1 + stringVariable2; 

Wtedy ci dwaj będą generować ten sam kod.

Chciałbym jednak wiedzieć, co dokładnie powiedzieli ci "wielu", ponieważ uważam, że jest inaczej.

To, co myślę, że powiedzieli, to że konkatenacja łańcuchów jest zła i powinieneś użyć StringBuilder lub podobnego.

Na przykład, jeśli to zrobić:

String s = "test"; 
for (int index = 1; index <= 10000; index++) 
    s = s + "test"; 

To co się dzieje, jest to, że dla każdej iteracji przez pętlę, można zbudować jeden nowy łańcuch i niech stara się kwalifikować do zbierania śmieci.

Dodatkowo, każdy taki nowy ciąg będzie zawierał całą zawartość starego, co oznacza, że ​​będziesz przenosić dużą ilość pamięci.

Zważywszy następującego kodu:

StringBuilder sb = new StringBuilder("test"); 
for (int index = 1; index <= 10000; index++) 
    sb.Append("test"); 

Czy zamiast użyć bufor wewnętrzny, który jest większy niż to, co musi być, tylko w przypadku trzeba dołączyć do niej więcej tekstu. Kiedy bufor się zapełni, zostanie przydzielony nowy, większy, a stary pozostanie do zbierania śmieci.

Jeśli chodzi o wykorzystanie pamięci i wykorzystanie procesora, późniejszy wariant jest znacznie lepszy.

Poza tym staram się unikać zbytniego skupiania się na "jest wariant kodu X lepszy niż Y", poza tym, z czym już masz doświadczenie. Na przykład używam StringBuilder teraz tylko dlatego, że jestem świadomy tej sprawy, ale to nie znaczy, że cały kod, który piszę, że go używa, faktycznie tego potrzebuje.

Staraj się nie tracić czasu na mikrooptymalizację kodu, dopóki nie zauważysz, że masz wąskie gardło. W tym czasie nadal obowiązuje zasada dotycząca pierwszego pomiaru, późniejszego cięcia.