Jaki jest najskuteczniejszy sposób łączenia łańcuchów?
c#
.net
string
optimization
jimmij
źródło
źródło
StringBuilder
przypadków użycia można znaleźć tutaj .String.Format
na sterydach. Co pod względem wydajności jest nieco wolniejsze na jednej linii niż+
iString.Concat
, ale znacznie lepsze niż te, choć wolniejsze niżStringBuilder
przy wielu połączeniach. Praktycznie rzecz biorąc, różnice w wydajności są takie, że gdybym musiał wybrać tylko jeden sposób konkatenacji, wybrałbym interpolacje ciągów przy użyciu$
... Jeśli dwa sposoby, dodajStringBuilder
do mojego zestawu narzędzi. Z tymi dwoma sposobami jesteś ustawiony.String.Join
odpowiedź nie oddaje+
sprawiedliwości i jest praktycznie złym sposobem łączenia łańcuchów, ale jest zaskakująco szybka pod względem wydajności. Odpowiedź dlaczego jest interesująca.String.Concat
iString.Join
oba mogą działać na tablicach, ale wString.Join
rzeczywistości jest szybszy. NajwyraźniejString.Join
jest dość wyrafinowany i bardziej zoptymalizowany niżString.Concat
, częściowo dlatego, że działa podobnie doStringBuilder
tego, że najpierw oblicza długość łańcucha, a następnie konstruuje ciąg korzystający z tej wiedzy za pomocą UnSafeCharBuffer.String.Join
wymaga także zbudowania tablicy, która wydaje się nieefektywna pod względem zasobów, prawda? ... Okazuje się+
iString.Concat
konstruuje tablice dla swoich składników. W związku z tym ręczne tworzenie tablicy i karmienie jejString.Join
jest stosunkowo szybsze ... jednakStringBuilder
wciąż przewyższaString.Join
praktycznie każdy praktyczny sposób, podczas gdy$
jest tylko nieco wolniejsze i znacznie szybsze przy długich ciągach… nie wspominając o tym, że korzystanie z niej jest niewygodne i brzydkie,String.Join
jeśli masz stworzyć na miejscu tablicę.Odpowiedzi:
Ta
StringBuilder.Append()
metoda jest znacznie lepsza niż korzystanie z+
operatora. Ale odkryłem, że wykonanie 1000 konkatenacji lub mniejString.Join()
jest jeszcze bardziej wydajne niżStringBuilder
.Jedynym problemem
String.Join
jest to, że musisz połączyć łańcuchy ze wspólnym separatorem.Edycja: jak wskazał @ryanversaw , możesz ustawić separator
string.Empty
.źródło
StringBuilder
ma ogromny porównywalny koszt rozruchu, jest skuteczny tylko przy użyciu bardzo dużych ciągów lub bardzo wielu konkatenacji. Nie jest trywialne ustalenie jakiejkolwiek sytuacji. Jeśli wydajność ma znaczenie, profilowanie jest Twoim przyjacielem (sprawdź ANTS).string.Concat
?Rico Mariani , guru ds. Wydajności .NET, napisał artykuł na ten temat. To nie jest tak proste, jak można się spodziewać. Podstawowa rada jest następująca:
Kolejny artykuł na poparcie tego twierdzenia pochodzi od Erica Lipperta, w którym szczegółowo opisuje optymalizacje przeprowadzone na
+
konkatenacjach jednej linii .źródło
Istnieje 6 rodzajów konkatenacji łańcuchów:
+
symbolu plus ( ).string.Concat()
.string.Join()
.string.Format()
.string.Append()
.StringBuilder
.W eksperymencie udowodniono, że
string.Concat()
jest to najlepszy sposób podejścia, jeśli słowa są mniejsze niż 1000 (w przybliżeniu) i jeśli słowa są większe niż 1000,StringBuilder
należy ich użyć.Aby uzyskać więcej informacji, sprawdź tę stronę .
źródło
+
był faktycznie 3 milisekund szybciej niżstring.Concat()
, choć nie wyglądał na wysokości strun wymaganej przedstring.Concat()
outraces+
.Od Chinh Do - StringBuilder nie zawsze jest szybszy :
Reguły kciuka
Podczas konkatenacji trzech wartości dynamicznych ciągów lub mniej, użyj tradycyjnej konkatenacji ciągów.
Podczas łączenia więcej niż trzech dynamicznych wartości ciągu użyj
StringBuilder
.Budując duży ciąg z kilku literałów łańcuchowych, użyj
@
literału łańcuchowego lub operatora inline +.Większość czasu
StringBuilder
jest najlepszym wyborem, ale są przypadki pokazane w tym poście, że powinieneś przynajmniej pomyśleć o każdej sytuacji.źródło
Jeśli pracujesz w pętli,
StringBuilder
prawdopodobnie jest to właściwa droga; oszczędza ci to nakładów związanych z regularnym tworzeniem nowych ciągów. W kodzie, który uruchomi się tylko raz,String.Concat
prawdopodobnie jest w porządku.Jednak Rico Mariani (guru optymalizacji .NET) przygotował quiz, w którym stwierdził na końcu, że w większości przypadków zaleca
String.Format
.źródło
Oto najszybsza metoda, którą opracowałem przez dekadę dla mojej aplikacji NLP na dużą skalę. Mam wariacje
IEnumerable<T>
i inne typy danych wejściowych, z separatorami różnych typów i bez nich (Char
,String
), ale tutaj pokazuję prosty przypadek połączenia wszystkich łańcuchów w tablicy w pojedynczy łańcuch bez separatora. Najnowsza wersja tutaj została opracowana i przetestowana na C # 7 i .NET 4.7 .Istnieją dwa klucze do wyższej wydajności; pierwszy polega na wstępnym obliczeniu wymaganego całkowitego rozmiaru. Ten krok jest trywialny, gdy dane wejściowe są tablicą, jak pokazano tutaj. Do obsługi
IEnumerable<T>
zamiast tego warto najpierw zebrać ciągi do tymczasowej tablicy do obliczenia tej sumy (tablica jest wymagana, aby uniknąć wywoływaniaToString()
więcej niż raz na element, ponieważ technicznie, biorąc pod uwagę możliwość wystąpienia efektów ubocznych, może to zmienić oczekiwaną semantykę operacji „łączenia ciągu”).Następnie, biorąc pod uwagę całkowitą wielkość alokacji końcowego łańcucha, największy wzrost wydajności uzyskuje się poprzez zbudowanie łańcucha wynikowego w miejscu . Wykonanie tego wymaga (być może kontrowersyjnej) techniki tymczasowego zawieszenia niezmienności nowego,
String
który początkowo ma przypisane zera. Pomijając wszelkie takie kontrowersje ...Pełny kod:
Powinienem wspomnieć, że ten kod ma niewielką modyfikację w stosunku do tego, z czego sam korzystam. W oryginale, ja wywołać cpblk IL dyspozycję z C # zrobić rzeczywiste kopiowanie. Dla uproszczenia i przenośności w kodzie tutaj zastąpiłem to P / Invoke
memcpy
, jak widać. Aby uzyskać najwyższą wydajność na x64 ( ale może nie x86 ), możesz zamiast tego użyć metody cpblk .źródło
string.Join
robi te wszystkie rzeczy już dla ciebie. Nie musisz pisać tego sam. Oblicza rozmiar końcowego łańcucha, konstruuje łańcuch o tym rozmiarze, a następnie zapisuje do podstawowej tablicy znaków. Ma nawet zaletę używania czytelnych nazw zmiennych w tym procesie.String.Join
może być wydajny. Jak wspomniałem we wstępie, kod tutaj jest tylko najprostszą ilustracją rodziny funkcji, których używam do scenariuszy, któreString.Join
albo nie obsługują (takie jak optymalizacja dlaChar
separatora), albo nie obsługiwały poprzednich wersji .NET. Przypuszczam, że nie powinienem był wybierać tego w najprostszym przykładzie, ponieważ jest to przypadek, któryString.Join
już dobrze sobie radzi, choć z „nieefektywnością”, prawdopodobnie niemożliwą do zmierzenia, w przetwarzaniu próżnego separatora, a mianowicie.String.Empty
.Concat
, co również robi to poprawnie. Tak czy inaczej, nie musisz sam pisać kodu.String.Join
mojego kodu za pomocą tego zestawu testowego . W przypadku 10 milionów losowych operacji konkatenacji, z których każda zawiera do 100 ciągów o wielkości słowa, powyższy kod jest konsekwentnie o 34% szybszy niżString.Join
w wersji x64 z .NET 4.7 . Ponieważ PO wyraźnie żąda metody „najbardziej wydajnej”, wynik sugeruje, że moja odpowiedź ma zastosowanie. Jeśli rozwiąże to twoje obawy, zapraszam do ponownego rozpatrzenia swojego zdania.Z tego artykułu MSDN :
Jeśli więc ufasz MSDN, skorzystaj z StringBuilder, jeśli musisz wykonać więcej niż 10 operacji / konkatenacji łańcuchów - w przeciwnym razie proste połączenie łańcuchów z '+' jest w porządku.
źródło
Ważne jest również, aby podkreślić, że powinieneś używać
+
operatora, jeśli łączysz literały łańcuchowe .Instrukcje: łączenie wielu ciągów (Podręcznik programowania w języku C #)
źródło
Dodając do innych odpowiedzi, należy pamiętać, że StringBuilder może otrzymać początkową ilość pamięci do przydzielenia .
Wielokrotne dołączanie do StringBuilder, który nie został wstępnie przydzielony, może powodować wiele niepotrzebnych przydziałów, podobnie jak powtarzające się regularne łączenie ciągów.
Jeśli wiesz, jak długi będzie końcowy ciąg, możesz go trywialnie obliczyć lub zgadnij, co to jest typowy przypadek (przydzielanie zbyt dużej liczby niekoniecznie jest złą rzeczą), powinieneś przekazać te informacje konstruktorowi lub Właściwość pojemności . Zwłaszcza podczas uruchamiania testów wydajności w celu porównania StringBuilder z innymi metodami, takimi jak String.Concat, które robią to samo wewnętrznie. Każdy test widziany online, który nie uwzględnia wstępnej alokacji StringBuilder w swoich porównaniach, jest nieprawidłowy.
Jeśli nie możesz zgadnąć, jaki jest rozmiar, prawdopodobnie piszesz funkcję narzędziową, która powinna mieć własny opcjonalny argument do kontroli wstępnej alokacji.
źródło
Poniżej może być jeszcze jedno alternatywne rozwiązanie do łączenia wielu ciągów.
interpolacja ciągów
źródło
String.Format
ale bardziej czytelny i łatwiejszy w obsłudze. Ława oznakowanie go, to nieco wolniej niż+
iString.Concat
na jednej linii powiązań, ale znacznie lepsze niż obie te w powtarzalnych połączeń wchodzącychStringBuilder
mniej konieczne.Najbardziej wydajne jest użycie StringBuilder, takie jak:
@jonezy: String.Concat jest w porządku, jeśli masz kilka małych rzeczy. Ale jeśli łączysz megabajty danych, Twój program prawdopodobnie się zapełni.
źródło
Wypróbuj 2 fragmenty kodu, a znajdziesz rozwiązanie.
Vs
Przekonasz się, że 1. kod skończy się naprawdę szybko, a pamięć będzie w sporej ilości.
Drugi kod może pamięć będzie w porządku, ale zajmie to dłużej ... znacznie dłużej. Więc jeśli masz aplikację dla wielu użytkowników i potrzebujesz prędkości, użyj 1.. Jeśli masz aplikację na krótki czas dla jednej aplikacji użytkownika, być może możesz użyć obu aplikacji lub druga będzie bardziej „naturalna” dla programistów.
Twoje zdrowie.
źródło
W przypadku tylko dwóch ciągów zdecydowanie nie chcesz używać StringBuilder. Istnieje pewien próg, powyżej którego obciążenie StringBuilder jest mniejsze niż obciążenie przydzielania wielu ciągów.
Tak więc, dla więcej niż 2-3 łańcuchów, użyj kodu DannySmurf . W przeciwnym razie wystarczy użyć operatora +.
źródło
System.String jest niezmienny. Kiedy modyfikujemy wartość zmiennej łańcuchowej, nowa pamięć jest przydzielana do nowej wartości i poprzedni przydział pamięci jest zwalniany. System.StringBuilder został zaprojektowany w taki sposób, aby miał pojęcie zmiennego ciągu, w którym można wykonywać różne operacje bez przydzielania osobnej lokalizacji pamięci dla zmodyfikowanego ciągu.
źródło
Inne rozwiązanie:
wewnątrz pętli użyj List zamiast łańcucha.
to jest bardzo, bardzo szybkie.
źródło
To naprawdę zależy od wzorca użytkowania. Szczegółowy test porównawczy między string.Join, string, Concat i string.Format można znaleźć tutaj: String.Format nie nadaje się do intensywnego rejestrowania
(To jest właściwie ta sama odpowiedź, którą dałem na to pytanie)
źródło
To zależy od kodu. StringBuilder jest ogólnie bardziej wydajny, ale jeśli łączysz tylko kilka łańcuchów i robisz to wszystko w jednym wierszu, optymalizacje kodu zapewnią to za Ciebie. Ważne jest, aby pomyśleć również o tym, jak wygląda kod: w przypadku większych zestawów StringBuilder ułatwi czytanie, w przypadku małych StringBuilder po prostu doda niepotrzebnego bałaganu.
źródło