Jest Sznurkiem.Format równie wydajny jak StringBuilder

Załóżmy, że mam stringbuilder w C#, który robi to:

StringBuilder sb = new StringBuilder();
string cat = "cat";
sb.Append("the ").Append(cat).(" in the hat");
string s = sb.ToString();

Czy byłoby to równie wydajne czy bardziej wydajne jak posiadanie:

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);
Jeśli tak, to dlaczego?

EDIT

Po kilku ciekawych odpowiedziach zdałem sobie sprawę, że prawdopodobnie powinienem być trochę jaśniejszy w tym, o co pytam. Nie tyle prosiłem o to, co było szybsze w konkatenacji sznurka, ale co jest szybsze w wstrzykiwanie jeden ciąg w drugi.

W obu przypadkach powyżej chcę wprowadzić jeden lub więcej łańcuchów w środku predefiniowanego łańcucha szablonu.

Przepraszam za zamieszanie

Author: James McMahon, 2008-08-09

12 answers

String.Format używa StringBuilder wewnętrznie:

public static string Format(IFormatProvider provider, string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }

    StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
    builder.AppendFormat(provider, format, args);
    return builder.ToString();
}

Powyższy kod jest fragmentem z mscorlib, więc pytanie brzmi " czy StringBuilder.Append() jest szybszy niż StringBuilder.AppendFormat()"?

Bez benchmarkingu powiedziałbym, że powyższy przykład kodu działa szybciej przy użyciu .Append(). Ale to zgadywanie, spróbuj benchmarkingu i / lub profilowania tych dwóch, aby uzyskać odpowiednie porównanie.

Ten koleś, Jerry Dixon, zrobił kilka porównań:

Http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

Aktualizacja:

Niestety powyższy link umarł. Jednak w drodze powrotnej jest jeszcze Kopia:

Http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

Pod koniec dnia zależy czy twój ciąg formatowanie będzie wywoływane powtarzalnie, tzn. wykonujesz poważne przetwarzanie tekstu ponad 100 megabajtów tekstu, lub czy jest wywoływane, gdy użytkownik kliknie przycisk od czasu do czasu. Chyba, że robisz jakieś wielkie przetwarzanie wsadowe, trzymałbym się Sznurka.Format, ułatwia czytelność kodu. Jeśli podejrzewasz, że jest to perf, to przyklej profiler do kodu i zobacz, gdzie naprawdę jest.

 138
Author: Kev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-07 17:59:06

Z dokumentacji MSDN :

Wydajność operacji konkatenacji dla obiektu String lub StringBuilder zależy od częstotliwości alokacji pamięci. Operacja konkatenacji ciągów zawsze przydziela pamięć, podczas gdy operacja konkatenacji StringBuilder przydziela pamięć tylko wtedy, gdy bufor obiektu StringBuilder jest zbyt mały, aby pomieścić nowe dane. W związku z tym Klasa String jest preferowana dla operacji konkatenacji, jeśli stała liczba Obiekty łańcuchowe są łączone. W takim przypadku poszczególne operacje konkatenacji mogą być nawet połączone w jedną operację przez kompilator. Obiekt StringBuilder jest preferowany do operacji konkatenacji, jeśli dowolna liczba łańcuchów jest skonkatenowana; na przykład, jeśli pętla łączy losową liczbę łańcuchów wprowadzonych przez użytkownika.

 43
Author: Greg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-09 14:36:45

Przeprowadziłem kilka szybkich testów wydajności i dla 100 000 operacji uśrednionych na 10 uruchomieniach, pierwsza metoda (String Builder) zajmuje prawie połowę czasu drugiej (String Format).

Więc, jeśli jest to rzadkie, to nie ma znaczenia. Ale jeśli jest to powszechna operacja, możesz użyć pierwszej metody.

 12
Author: Vaibhav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-09 19:34:51

Spodziewałbym się String.Format aby był wolniejszy - musi on przetworzyć łańcuch i Następnie połączyć go.

Kilka uwag:

  • Format jest sposobem, aby przejść do widocznych przez użytkownika ciągów w profesjonalnych aplikacjach; pozwala to uniknąć błędów lokalizacyjnych
  • Jeśli znasz długość wynikowego ciągu, użyj konstruktora StringBuilder (Int32) , aby predefiniować pojemność
 10
Author: McDowell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-09 17:09:10

Myślę, że w większości takich przypadków jasność, a nie wydajność, powinna być twoim największym zmartwieniem. Chyba że kruszysz razem Tony sznurków lub budujesz coś dla urządzenia mobilnego o niższej mocy, prawdopodobnie nie będzie to miało większego wpływu na szybkość biegu.

Odkryłem, że w przypadkach, gdy buduję ciągi w dość liniowy sposób, albo robienie prostych konkatenacji, albo używanie Stringbuildera jest najlepszą opcją. Proponuję to w przypadkach, gdy większość ciągów to, że budujesz jest dynamiczne. Ponieważ bardzo niewiele tekstu jest statycznych, najważniejsze jest to, że jest jasne, gdzie każdy fragment dynamicznego tekstu jest umieszczany w razie potrzeby aktualizacji w przyszłości.

Z drugiej strony, jeśli mówisz o dużym kawałku statycznego tekstu z dwiema lub trzema zmiennymi, nawet jeśli jest to trochę mniej efektywne, myślę, że przejrzystość zyskujesz dzięki string.Format sprawia, że warto. Użyłem tego wcześniej w tym tygodniu, gdy musiałem umieścić jeden kawałek dynamicznego tekst na środku 4-stronicowego dokumentu. Łatwiej będzie zaktualizować ten duży kawałek tekstu, jeśli jest w jednym kawałku, niż aktualizować trzy kawałki, które łączysz ze sobą.

 8
Author: saalon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-09 14:40:00

If only because string.Format nie robi dokładnie tego, co myślisz, oto powtórka testów 6 lat później na Net45.

Concat jest wciąż najszybszy, ale tak naprawdę to mniej niż 30% różnicy. StringBuilder i Format różnią się zaledwie o 5-10%. Mam wariacje 20% przeprowadzając testy kilka razy.

Milisekundy, milion iteracji:

  • konkatenacja: 367
  • Nowy stringBuilder dla każdego klucza: 452
  • Cached StringBuilder: 419
  • string.Format: 475

Lekcja, którą zabieram jest taka, że różnica w wydajności jest trywialna, więc nie powinno to powstrzymywać cię od pisania najprostszego czytelnego kodu, jaki możesz. Co za moje pieniądze jest często, ale nie zawsze a + b + c.

const int iterations=1000000;
var keyprefix= this.GetType().FullName;
var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations);
Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength);

var concatkeys= new string[iterations];
var stringbuilderkeys= new string[iterations];
var cachedsbkeys= new string[iterations];
var formatkeys= new string[iterations];

var stopwatch= new System.Diagnostics.Stopwatch();
Console.WriteLine("Concatenation:");
stopwatch.Start();

for(int i=0; i<iterations; i++){
    var key1= keyprefix+":" + i.ToString();
    concatkeys[i]=key1;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("New stringBuilder for each key:");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString();
    stringbuilderkeys[i]= key2;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("Cached StringBuilder:");
var cachedSB= new StringBuilder(maxkeylength);
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString();
    cachedsbkeys[i]= key2b;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("string.Format");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key3= string.Format("{0}:{1}", keyprefix,i.ToString());
    formatkeys[i]= key3;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-');
Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);
 7
Author: Chris F Carroll,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-07 18:00:17

String.Format używa StringBuilder internally...so logicznie to prowadzi do idei, że byłoby to trochę mniej wydajne ze względu na więcej kosztów. Jednak prosta konkatenacja ciągów jest najszybszą metodą wtryskiwania jednego ciągu pomiędzy dwa others...by w znacznym stopniu. Dowód ten udowodnił Rico Mariani w swoim pierwszym quizie performatywnym sprzed lat. Prostym faktem jest, że konkatenacje...gdy znana jest liczba części łańcucha (bez ograniczeń..można konkatenować a tysiąc parts...as o ile znasz jego zawsze 1000 części)...są zawsze szybsze niż {[2] } lub String.Format. Mogą być wykonywane z pojedynczą alokacją pamięci lub serią kopii pamięci. tutaj jest dowód

I tutaj jest rzeczywisty kod dla jakiegoś ciągu.Metody Concat, które ostatecznie wywołują FillStringChecked, który używa wskaźników do kopiowania pamięci (wyodrębnionej przez Reflektor):

public static string Concat(params string[] values)
{
    int totalLength = 0;

    if (values == null)
    {
        throw new ArgumentNullException("values");
    }

    string[] strArray = new string[values.Length];

    for (int i = 0; i < values.Length; i++)
    {
        string str = values[i];
        strArray[i] = (str == null) ? Empty : str;
        totalLength += strArray[i].Length;

        if (totalLength < 0)
        {
            throw new OutOfMemoryException();
        }
    }

    return ConcatArray(strArray, totalLength);
}

public static string Concat(string str0, string str1, string str2, string str3)
{
    if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null)))
    {
        return Empty;
    }

    if (str0 == null)
    {
        str0 = Empty;
    }

    if (str1 == null)
    {
        str1 = Empty;
    }

    if (str2 == null)
    {
        str2 = Empty;
    }

    if (str3 == null)
    {
        str3 = Empty;
    }

    int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length;
    string dest = FastAllocateString(length);
    FillStringChecked(dest, 0, str0);
    FillStringChecked(dest, str0.Length, str1);
    FillStringChecked(dest, str0.Length + str1.Length, str2);
    FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3);
    return dest;
}

private static string ConcatArray(string[] values, int totalLength)
{
    string dest = FastAllocateString(totalLength);
    int destPos = 0;

    for (int i = 0; i < values.Length; i++)
    {
        FillStringChecked(dest, destPos, values[i]);
        destPos += values[i].Length;
    }

    return dest;
}

private static unsafe void FillStringChecked(string dest, int destPos, string src)
{
    int length = src.Length;

    if (length > (dest.Length - destPos))
    {
        throw new IndexOutOfRangeException();
    }

    fixed (char* chRef = &dest.m_firstChar)
    {
        fixed (char* chRef2 = &src.m_firstChar)
        {
            wstrcpy(chRef + destPos, chRef2, length);
        }
    }
}

Zatem:

string what = "cat";
string inthehat = "The " + what + " in the hat!";
Smacznego!
 5
Author: jrista,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-07 17:59:44

Och również, najszybszy byłby:

string cat = "cat";
string s = "The " + cat + " in the hat";
 3
Author: Vaibhav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-09 19:51:24

To naprawdę zależy. W przypadku małych strun z kilkoma konkatenacjami, właściwie szybsze jest tylko dołączanie strun.

String s = "String A" + "String B";

Ale dla większych ciągów (bardzo bardzo dużych ciągów), bardziej wydajne jest użycie Stringbuildera.

 0
Author: Joseph Daigle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-09 14:31:51

W obu przypadkach powyżej chcę wprowadzić jeden lub więcej łańcuchów w środku predefiniowanego łańcucha szablonu.

W takim przypadku sugerowałbym String.Format jest najszybszy, ponieważ jest zaprojektowany dokładnie do tego celu.

 0
Author: GateKiller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-09 16:29:55

To naprawdę zależy od Twojego wzorca użytkowania.
Szczegółowy benchmark pomiędzy string.Join, string,Concat oraz string.Format można znaleźć tutaj: String.Format Nie nadaje się do intensywnego logowania

 0
Author: Liran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-01 18:40:40

Sugerowałbym nie, ponieważ String.Format nie był przeznaczony do konkatenacji, był przeznaczony do formatowania wyjścia różnych wejść, takich jak data.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
 -1
Author: GateKiller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-16 07:19:40