Wypróbuj ten kod.
To nieco zmodyfikowana wersja Twojego kodu.
1. Usunąłem Console.WriteLine, ponieważ prawdopodobnie jest o kilka rzędów wielkości wolniejszy niż to, co próbuję zmierzyć.
2. Uruchamiam Stopwatch przed pętlą i zatrzymuję go zaraz po tym, dzięki czemu nie tracę precyzji, jeśli wykonanie funkcji wymaga np. 26,4 tików.
3. Sposób podzielenia wyniku przez kilka iteracji był nieprawidłowy. Zobacz, co się stanie, jeśli masz 1000 milisekund i 100 milisekund. W obu sytuacjach po podzieleniu przez 1000000 otrzymasz 0 ms.
Stopwatch s = new Stopwatch();
var p = new { FirstName = "Bill", LastName = "Gates" };
int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;
string result;
s.Start();
for (var i = 0; i < n; i++)
result = (p.FirstName + " " + p.LastName);
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();
s.Start();
for (var i = 0; i < n; i++)
result = string.Format("{0} {1}", p.FirstName, p.LastName);
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();
Console.Clear();
Console.WriteLine(n.ToString()+" x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Thread.Sleep(4000);
Oto moje wyniki:
1000000 x wynik = string.Format ("{0} {1}", p.FirstName, p.LastName); zajęło: 618 ms - 2213706 taktów
1000000 x wynik = (p.FirstName + "" + p.LastName); wziął: 166ms - 595610 ticków
string.Format
które nie używa żadnych funkcji formatowania kompozytowego (tj. Jest po prostu proste{0}
) i zastępuje je znacznie szybszą konkatenacją ciągów. Zastanawiam się, że taki wyczyn jest możliwy do osiągnięcia przy użyciu istniejącego narzędzia do ponownego pisania IL, takiego jak PostSharp.Jestem zdumiony, że tak wiele osób od razu chce znaleźć kod, który wykonuje się najszybciej. Jeśli przetworzenie JEDNEGO MILIONA iteracji NADAL zajmie mniej niż sekundę, czy będzie to JAKIEKOLWIEK zauważalne dla użytkownika końcowego? Niezbyt prawdopodobne.
Poszedłbym z
String.Format
opcję tylko dlatego, że ma to największy sens z architektonicznego punktu widzenia. Nie obchodzi mnie wydajność, dopóki nie stanie się to problemem (a jeśli tak, zadałbym sobie pytanie: czy muszę łączyć milion nazwisk naraz? Z pewnością nie wszystkie zmieszczą się na ekranie ...)Zastanów się, czy klient później chce to zmienić, aby mógł skonfigurować, czy ma wyświetlać,
"Firstname Lastname"
czy"Lastname, Firstname."
z opcją Format, jest to łatwe - po prostu zamień ciąg formatu. Do konkatacji potrzebny będzie dodatkowy kod. Jasne, że w tym konkretnym przykładzie nie brzmi to jak wielka sprawa, ale ekstrapoluj.źródło
Ojej - po przeczytaniu jednej z pozostałych odpowiedzi próbowałem odwrócić kolejność operacji - więc najpierw wykonałem konkatenację, a potem String.Format ...
Tak więc kolejność operacji robi OGROMNĄ różnicę, a raczej pierwsza operacja jest ZAWSZE znacznie wolniejsza.
Oto wyniki przebiegu, w którym operacje są wykonywane więcej niż raz. Próbowałem zmienić rozkazy, ale ogólnie rzecz biorąc, obowiązują te same zasady, gdy pierwszy wynik jest ignorowany:
Jak widać, kolejne uruchomienia tej samej metody (refaktoryzowałem kod na 3 metody) są przyrostowo szybsze. Najszybszą wydaje się być metoda Console.WriteLine (String.Concat (...)), po której następuje zwykłe konkatenowanie, a następnie operacje sformatowane.
Początkowe opóźnienie w uruchomieniu jest prawdopodobnie związane z inicjalizacją Console Stream, ponieważ umieszczenie Console.Writeline („Start!”) Przed pierwszą operacją przywraca wszystkie czasy do linii.
źródło
Ciągi znaków są niezmienne, co oznacza, że ten sam mały fragment pamięci jest używany w kodzie w kółko. Dodawanie tych samych dwóch ciągów do siebie i ciągłe tworzenie tego samego nowego ciągu nie ma wpływu na pamięć. .Net jest wystarczająco inteligentny, aby używać tego samego odniesienia do pamięci. Dlatego twój kod tak naprawdę nie testuje różnicy między dwiema metodami konkatowania.
Wypróbuj ten rozmiar:
Przykładowe dane wyjściowe:
źródło
string.Format
jest warte niewielkiej wydajności tutaj. Architektonicznie jest to lepsze, ponieważ oznacza, że możesz łatwiej zmieniać format. Ale budowniczy ciągów, naprawdę nie widzę sensu. Każdy inny wątek tutaj mówi, że powinieneś użyć Stringbuildera zamiast łączenia ciągów. Jaka jest zaleta? Najwyraźniej nie szybkości, jak pokazuje ten wzorzec.Szkoda biednych tłumaczy
Jeśli ty wiesz że Twoja aplikacja pozostanie w języku angielskim, to dobrze, zachowaj tyknięcia zegara. Jednak w wielu kulturach nazwisko nazwisko imię jest zwykle widoczne na przykład w adresach.
Więc używaj
string.Format()
, zwłaszcza jeśli kiedykolwiek będziesz mieć swoją aplikację wszędzie, gdzie angielski nie jest pierwszym językiem.źródło
string.Format()
zachowywałby się inaczej w różnych kulturach? Czy nadal nie wypisuje imienia, a następnie nazwiska? Wygląda na to, że w obu sytuacjach należałoby wziąć pod uwagę odmienną kulturę. Czuję, że czegoś mi tu brakuje.string.Format()
możesz wiedzieć, że używasz nazwy dla adresu? Gdybystring.Format()
zamienić{0} {1}
na podstawie kultury, uznałbym to za zepsute.Oto moje wyniki po 100 000 iteracji:
A oto kod stanowiska:
Nie wiem więc, czyją odpowiedź oznaczyć jako odpowiedź :)
źródło
Łączenie ciągów jest w porządku w takim prostym scenariuszu - jest to bardziej skomplikowane z czymś bardziej skomplikowanym, nawet z LastName, FirstName. Dzięki formatowi możesz na pierwszy rzut oka zobaczyć, jaka będzie ostateczna struktura łańcucha podczas czytania kodu, przy konkatenacji prawie niemożliwe jest natychmiastowe rozpoznanie końcowego wyniku (z wyjątkiem bardzo prostego przykładu, takiego jak ten).
Na dłuższą metę oznacza to, że kiedy wrócisz, aby zmienić format ciągu, będziesz mieć możliwość wpadnięcia i wprowadzenia kilku poprawek w ciągu formatu, albo zmarszczysz brwi i zaczniesz się poruszać rodzaje metod dostępu do właściwości zmieszane z tekstem, co z większym prawdopodobieństwem spowoduje problemy.
Jeśli używasz .NET 3.5, możesz użyć metody rozszerzenia, takiej jak ta i uzyskać łatwy przepływ, poza składnią mankietu, w następujący sposób:
Wreszcie, gdy Twoja aplikacja staje się coraz bardziej złożona, możesz zdecydować, że aby rozsądnie zarządzać ciągami znaków w aplikacji, chcesz przenieść je do pliku zasobów w celu lokalizacji lub po prostu do statycznego pomocnika. Będzie to DUŻO łatwiejsze do osiągnięcia, jeśli konsekwentnie używasz formatów, a twój kod może być po prostu refaktoryzowany, aby użyć czegoś takiego jak
źródło
Do bardzo prostej manipulacji użyłbym konkatenacji, ale gdy przekroczysz 2 lub 3 elementy Format staje się bardziej odpowiedni IMO.
Innym powodem, dla którego warto preferować String.Format, jest to, że ciągi .NET są niezmienne i robiąc to w ten sposób, tworzy mniej kopii tymczasowych / pośrednich.
źródło
Chociaż całkowicie rozumiem preferencje stylu i wybrałem konkatenację dla mojej pierwszej odpowiedzi, częściowo w oparciu o moje własne preferencje, część mojej decyzji była oparta na myśli, że konkatenacja będzie szybsza. Tak więc z ciekawości przetestowałem go i wyniki były oszałamiające, szczególnie dla tak małego sznurka.
Używając poniższego kodu:
Otrzymałem następujące wyniki:
Korzystanie z metody formatowania jest ponad 100 razy wolniejsze! Konkatenacja nie została nawet zarejestrowana jako 1ms, dlatego też wyprowadzam tyknięcia timera.
źródło
Począwszy od C # 6,0 interpolowanych ciągów, można to zrobić, co jeszcze bardziej upraszcza format.
Ciągi interpolowane mają podobną wydajność do String.Format, ale poprawioną czytelność i krótszą składnię ze względu na fakt, że wartości i wyrażenia są wstawiane w linii.
Zapoznaj się również z tym artykułem dotnetperls dotyczącym interpolacji ciągów.
Jeśli szukasz domyślnego sposobu formatowania ciągów, ma to sens pod względem czytelności i wydajności (z wyjątkiem sytuacji, gdy mikrosekundy będą miały wpływ na twój konkretny przypadek użycia).
źródło
Do podstawowej konkatenacji ciągów używam zazwyczaj drugiego stylu - łatwiejszego do odczytania i prostszego. Jeśli jednak wykonuję bardziej skomplikowaną kombinację ciągów, zwykle wybieram String.Format.
String.Format oszczędza wiele cytatów i plusów ...
Zapisano tylko kilka postaci, ale myślę, że w tym przykładzie format sprawia, że jest znacznie czystszy.
źródło
Lepszym testem byłoby obserwowanie pamięci za pomocą Perfmon i liczników pamięci CLR. Rozumiem, że cały powód, dla którego chcesz używać String.Format zamiast po prostu łączyć ciągi, jest taki, że ponieważ ciągi są niezmienne, niepotrzebnie obciążasz moduł odśmiecania tymczasowymi ciągami, które muszą zostać odzyskane w następnym przebiegu.
StringBuilder i String.Format, chociaż potencjalnie wolniejsze, są bardziej wydajne w pamięci.
Co jest takiego złego w konkatenacji ciągów?
źródło
Generalnie wolę ten pierwszy, ponieważ szczególnie przy długich strunach może być dużo łatwiejszy do odczytania.
Inną korzyścią jest, moim zdaniem, jedną z wydajności, ponieważ ta ostatnia faktycznie wykonuje 2 instrukcje tworzenia ciągu przed przekazaniem końcowego ciągu do metody Console.Write. Uważam, że String.Format używa StringBuilder pod okładkami, więc unika się wielokrotnych konkatenacji.
Należy jednak zauważyć, że jeśli parametry, które przekazujesz do String.Format (i inne takie metody, takie jak Console.Write) są typami wartości, zostaną one opakowane przed przekazaniem, co może zapewnić własne wyniki wydajności. Post na blogu na ten temat tutaj .
źródło
Za tydzień od teraz 19 sierpnia 2015 to pytanie będzie miało dokładnie siedem (7) lat. Jest teraz lepszy sposób na zrobienie tego. Lepsze pod względem łatwości utrzymania, ponieważ nie wykonałem żadnego testu wydajności w porównaniu do zwykłego łączenia ciągów (ale czy ma to znaczenie w dzisiejszych czasach? Różnica kilku milisekund?). Nowy sposób na zrobienie tego w C # 6.0 :
Ta nowa funkcja jest lepsza , IMO, a właściwie lepsza w naszym przypadku, ponieważ mamy kody, w których budujemy kwerendy, których wartości zależą od pewnych czynników. Wyobraź sobie jedno pytanie, w którym mamy 6 argumentów. Więc zamiast robić na przykład:
in można napisać w ten sposób i jest to łatwiejsze do odczytania:
źródło
źródło
Wybieram na podstawie czytelności. Wolę opcję formatowania, gdy wokół zmiennych jest jakiś tekst. W tym przykładzie:
rozumiesz znaczenie nawet bez nazw zmiennych, podczas gdy concat jest zaśmiecony cudzysłowami i znakami + i dezorientuje moje oczy:
(Pożyczyłem przykład Mike'a, ponieważ mi się podoba)
Jeśli ciąg formatu niewiele znaczy bez nazw zmiennych, muszę użyć concat:
Opcja formatu sprawia, że czytam nazwy zmiennych i odwzorowuję je na odpowiadające im liczby. Opcja concat tego nie wymaga. Nadal jestem zdezorientowany cytatami i znakami +, ale alternatywa jest gorsza. Rubin?
Jeśli chodzi o wydajność, oczekuję, że opcja formatowania będzie wolniejsza niż konkatacja, ponieważ format wymaga przeanalizowania ciągu . Nie pamiętam, żebym musiał optymalizować tego rodzaju instrukcje, ale gdybym to zrobił, spojrzałbym na
string
metody takie jakConcat()
iJoin()
.Inną zaletą formatu jest to, że ciąg formatu można umieścić w pliku konfiguracyjnym. Bardzo przydatne z komunikatami o błędach i tekstem interfejsu użytkownika.
źródło
Użyłbym String.Format, ale chciałbym również mieć ciąg formatu w plikach zasobów, aby można go było zlokalizować dla innych języków. Używanie prostego konkatowania ciągów nie pozwala na to. Oczywiście, jeśli nigdy nie będziesz musiał lokalizować tego ciągu, nie jest to powód do myślenia. To naprawdę zależy od tego, do czego służy sznurek.
Jeśli miałoby to zostać pokazane użytkownikowi, użyłbym String.Format, aby móc zlokalizować, jeśli zajdzie taka potrzeba - a FxCop na wszelki wypadek sprawdzi to za mnie.
Jeśli zawiera liczby lub inne elementy niebędące ciągami (np. Daty), użyłbym String.Format, ponieważ zapewnia mi większą kontrolę nad formatowaniem .
Jeśli służy do tworzenia zapytania takiego jak SQL, użyłbym Linq .
Jeśli do łączenia ciągów w pętli użyłbym StringBuilder aby uniknąć problemów z wydajnością.
Jeśli chodzi o jakieś wyjście, którego użytkownik nie zobaczy i nie będzie miało wpływu na wydajność, użyłbym String.Format, ponieważ i tak mam zwyczaj go używać i po prostu jestem do tego przyzwyczajony :)
źródło
Jeśli masz do czynienia z czymś, co musi być łatwe do odczytania (a to jest większość kodu), trzymałbym się wersji przeciążenia operatora, chyba że:
W co najmniej dwóch z tych okoliczności użyłbym zamiast tego StringBuilder.
źródło
Jeśli zamierzasz zlokalizować wynik, to String.Format jest niezbędny, ponieważ różne języki naturalne mogą nawet nie mieć danych w tej samej kolejności.
źródło
Myślę, że zależy to w dużej mierze od tego, jak skomplikowany jest wynik. Wybieram ten scenariusz, który w danym momencie działa najlepiej.
Wybierz odpowiednie narzędzie w zależności od zadania: D Cokolwiek wygląda na najczystsze!
źródło
Wolę również drugie, ale nie mam obecnie racjonalnych argumentów na poparcie tego stanowiska.
źródło
Niezłe!
Właśnie dodane
I jest jeszcze szybszy (chyba w obu przykładach wywoływany jest string.Concat, ale pierwszy wymaga jakiegoś tłumaczenia).
źródło
string.Concat(...)
. Jest to wykonywane podczas kompilacji, więc nie ma wpływu na wydajność w czasie wykonywania. Jeśli uruchomisz testy wiele razy lub uruchomisz je na większych próbkach testowych, zobaczysz, że są identyczne.Ponieważ nie sądzę, aby odpowiedzi tutaj obejmowały wszystko, chciałbym dodać tutaj mały dodatek.
Console.WriteLine(string format, params object[] pars)
wezwaniastring.Format
. Znak „+” oznacza konkatenację ciągów. Nie sądzę, że zawsze ma to związek ze stylem; Zwykle mieszam te dwa style w zależności od kontekstu, w którym się znajduję.Krótka odpowiedź
Decyzja, przed którą stoisz, ma związek z alokacją ciągów. Postaram się to uprościć.
Powiedz, że masz
Jeśli to wykonasz, oceni to w następujący sposób:
tmp
tutaj nie jest tak naprawdę zmienną lokalną, ale jest ona tymczasowa dla JIT (jest umieszczana na stosie IL). Jeślildstr
umieścisz łańcuch na stosie (na przykład w IL dla literałów), umieszczasz odwołanie do wskaźnika ciągu na stosie.Moment wywołania
concat
tego odwołania staje się problemem, ponieważ nie ma żadnego dostępnego odwołania do łańcucha, które zawiera oba ciągi. Oznacza to, że .NET musi przydzielić nowy blok pamięci, a następnie wypełnić go dwoma ciągami. Powodem tego jest problem, ponieważ alokacja jest stosunkowo droga.Co zmienia pytanie na: Jak zmniejszyć liczbę
concat
operacji?Tak więc przybliżona odpowiedź brzmi:
string.Format
dla> 1 konkatacji „+” będzie działać dobrze na 1 konkat. A jeśli nie zależy ci na wykonywaniu mikrooptymalizacji wydajności,string.Format
w ogólnym przypadku będzie działać dobrze.Uwaga o kulturze
A potem jest coś, co nazywa się kulturą ...
string.Format
umożliwia korzystanieCultureInfo
w formatowaniu. Prosty operator „+” używa bieżącej kultury.Jest to szczególnie ważna uwaga, jeśli piszesz formaty plików i np.
double
wartości, które „dodajesz” do ciągu. Na różnych komputerach możesz skończyć z różnymi łańcuchami, jeśli nie używaszstring.Format
z jawnymCultureInfo
.F.ex. zastanów się, co się stanie, jeśli zmienisz „.” zamiast „,” podczas pisania pliku z wartościami oddzielonymi przecinkami ... w języku holenderskim separatorem dziesiętnym jest przecinek, więc użytkownik może po prostu otrzymać „zabawną” niespodziankę.
Bardziej szczegółowa odpowiedź
Jeśli nie znasz wcześniej dokładnego rozmiaru łańcucha, najlepiej użyć takiej zasady, aby nadać przydział buforów, których używasz. Wolna przestrzeń jest najpierw wypełniana, po czym dane są kopiowane.
Powiększanie oznacza przydzielanie nowego bloku pamięci i kopiowanie starych danych do nowego bufora. Następnie można zwolnić stary blok pamięci. W tym momencie dochodzisz do wniosku: uprawa to kosztowna operacja.
Najbardziej praktycznym sposobem na to jest użycie zasad nadmiernej alokacji. Najbardziej powszechną polityką jest nadawanie buforów potęgom 2. Oczywiście musisz to zrobić trochę mądrzej (ponieważ nie ma sensu rosnąć z 1,2,4,8 jeśli już wiesz, że potrzebujesz 128 znaków ), ale masz obraz. Polityka zapewnia, że nie potrzebujesz zbyt wielu kosztownych operacji, które opisałem powyżej.
StringBuilder
jest klasą, która w zasadzie z nadmierną alokacją bazowego bufora potęgami dwóch.string.Format
używaStringBuilder
pod maską.To sprawia, że Twoja decyzja jest podstawowym kompromisem między nadmierną alokacją i dołączaniem (-multiple) (w / wo culture) lub po prostu przydziel i dołącz.
źródło
Osobiście, drugi, ponieważ wszystko, czego używasz, jest w bezpośredniej kolejności, w jakiej zostanie wyprowadzony. Podczas gdy w przypadku pierwszego musisz dopasować {0} i {1} z odpowiednią zmienną, co łatwo zepsuć.
Przynajmniej nie jest tak źle, jak sprintf w C ++, gdzie jeśli źle ustawisz typ zmiennej, wszystko wybuchnie.
Ponadto, ponieważ druga jest w całości wbudowana i nie musi wyszukiwać ani zastępować wszystkich {0} rzeczy, ta ostatnia powinna być szybsza… chociaż nie wiem na pewno.
źródło
Właściwie podoba mi się ten pierwszy, ponieważ gdy jest wiele zmiennych przeplatanych z tekstem, wydaje mi się łatwiejszy do odczytania. Poza tym łatwiej jest radzić sobie z cudzysłowami, gdy używa się string.Format (), uh, format. Oto przyzwoita analiza konkatenacji ciągów.
źródło
Zawsze korzystałem z trasy string.Format (). Możliwość przechowywania formatów w zmiennych, takich jak przykład Nathana, jest wielką zaletą. W niektórych przypadkach mogę dołączyć zmienną, ale raz więcej niż 1 zmienna jest łączona i refaktoryzuję, aby użyć formatowania.
źródło
Aha, i tylko dla kompletności, poniższe jest kilka taktów szybciej niż zwykłe konkatenacje:
źródło
Pierwszy (format) wygląda mi lepiej. Jest bardziej czytelny i nie tworzysz dodatkowych tymczasowych obiektów typu string.
źródło
Byłem ciekawy, jak wygląda StringBuilder z tymi testami. Wyniki poniżej ...
Wyniki:
źródło
Zgodnie z materiałem przygotowawczym MCSD, Microsoft sugeruje użycie operatora + w przypadku bardzo małej liczby konkatenacji (prawdopodobnie 2 do 4). Nadal nie jestem pewien dlaczego, ale warto to rozważyć.
źródło