String.Format, StringBuilder kadar verimli mi?


160

Diyelim ki bunu yapan bir dize yapıcı C # var:

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

bu, aşağıdakilere sahip olmak kadar verimli veya daha verimli olur:

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);

Öyleyse neden?

DÜZENLE

Bazı ilginç cevaplardan sonra, sorduğum şeyde muhtemelen biraz daha net olmalıydım. Ben çok bir dize bitirme daha hızlı, ama hangi bir dize diğerine enjekte daha hızlı soran değildi .

Yukarıdaki her iki durumda da, önceden tanımlanmış bir şablon dizesinin ortasına bir veya daha fazla dizgi enjekte etmek istiyorum.

Karışıklık için özür dilerim


İlerideki iyileştirmelere izin vermek için lütfen bunları açık bırakın.
Mark Biek

4
Özel durum senaryosunda, en hızlısı bunlardan hiçbiri değildir: değiştirilecek parçanın boyutu yeni parçaya eşitse, dizeyi yerinde değiştirebilirsiniz. Ne yazık ki, bu yansıma veya güvensiz kod gerektirir ve dizenin değişmezliğini kasten ihlal eder. İyi bir uygulama değil, ama hız bir sorunsa ... :)
Abel

Yukarıda verilen örnekte string s = "The "+cat+" in the hat";bu, bir döngü içinde kullanılmadığı sürece en hızlı olabilir ki bu durumda en hızlı bir olacak StringBuilder döngü dışında başlatılır.
Surya Pratap

Yanıtlar:


146

Not: Bu yanıt, .NET 2.0 geçerli sürüm olduğunda yazılmıştır. Bu, daha sonraki sürümler için geçerli olmayabilir.

String.FormatStringBuilderdahili olarak kullanır :

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();
}

Yukarıdaki kod mscorlib'den bir parçacık, bu yüzden soru "daha StringBuilder.Append()hızlı StringBuilder.AppendFormat()" olur?

Kıyaslama olmadan muhtemelen yukarıdaki kod örneği kullanarak daha hızlı çalışacağını söyleyebilirim .Append(). Ama bu bir tahmin, doğru bir karşılaştırma elde etmek için kıyaslama ve / veya ikisini profilleme deneyin.

Bu adam Jerry Dixon, bazı kıyaslamalar yaptı:

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

Güncellenmiş:

Ne yazık ki yukarıdaki bağlantı o zamandan beri öldü. Ancak Geri Dönüş Makinesi'nde hala bir kopya var:

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

Günün sonunda, dize biçimlendirmenizin tekrar tekrar çağrılıp çağrılmayacağına, yani 100 megabaytlık metnin üzerinde ciddi bir metin işleme yapıp yapmadığınıza veya kullanıcı bir düğmeyi tekrar tekrar tıkladığında çağrılıp çağrılmadığına bağlıdır. Eğer bazı büyük toplu işlem işi yapmazsanız String.Format ile yapışırdım, kod okunabilirliğine yardımcı olur. Mükemmel bir darboğazdan şüpheleniyorsanız, kodunuza bir profiler yapıştırın ve gerçekten nerede olduğunu görün.


8
Jerry Dixon'un sayfasındaki kriterlerle ilgili bir sorun .ToString(), StringBuildernesneyi asla çağırmamasıdır . Pek çok yinelemede, o zaman büyük bir fark yaratıyor ve elmaları elmalar ile kıyaslayamadığı anlamına geliyor. Bu yüzden bu kadar büyük bir performans sergilediğini StringBuilderve muhtemelen sürprizini açıkladı. Sadece o hatayı düzelterek kriter tekrarlanan ve beklenen sonuçları aldı: String +operatörü en hızlı, izledi StringBuilder, ile String.Formatarka yetiştirme.
Ben Collins

5
6 yıl sonra, bu artık pek de öyle değil. Net4'te string.Format (), yeniden kullandığı bir StringBuilder örneği oluşturur ve önbelleğe alır, bu nedenle bazı test durumlarında StringBuilder'dan daha hızlı olabilir. Aşağıdaki cevaba revize bir kıyaslama koydum (bu hala concat'ın en hızlı olduğunu ve test durumum için formatın StringBuilder'dan% 10 daha yavaş olduğunu söylüyor).
Chris F Carroll

45

Gönderen MSDN belgelerine :

Bir String veya StringBuilder nesnesi için bir birleştirme işleminin performansı, bellek ayırmanın ne sıklıkta gerçekleştiğine bağlıdır. Bir String birleştirme işlemi her zaman bellek ayırırken, bir StringBuilder birleştirme işlemi yalnızca StringBuilder nesne arabelleği yeni verileri alacak kadar küçükse belleği ayırır. Sonuç olarak, sabit sayıda String nesnesi birleştirilirse, String sınıfı bir birleştirme işlemi için tercih edilir. Bu durumda, bireysel birleştirme işlemleri derleyici tarafından tek bir işlemde bile birleştirilebilir. Bir StringBuilder nesnesi, rastgele sayıda dize birleştirilirse bir birleştirme işlemi için tercih edilir; örneğin, bir döngü rasgele sayıda kullanıcı girişi dizesini birleştirirse.


12

Bazı hızlı performans ölçütleri çalıştırdım ve 10 çalıştırmada ortalama 100.000 işlem için, ilk yöntem (String Builder) ikincinin neredeyse yarısını alır (String Formatı).

Yani, bu nadirse, önemli değil. Ancak bu ortak bir işlemse, ilk yöntemi kullanmak isteyebilirsiniz.


10

String.Format'ın daha yavaş olmasını beklerdim - dizeyi ayrıştırmak ve sonra birleştirmek zorundadır .

Birkaç not:

  • Biçim , profesyonel uygulamalarda kullanıcı tarafından görülebilir dizelere gitmenin yoludur; bu, yerelleştirme hatalarını önler
  • Sonuçta elde edilen dizenin uzunluğunu önceden biliyorsanız , kapasiteyi önceden tanımlamak için StringBuilder (Int32) yapıcısını kullanın

8

Verimlilik değil, bu netlik gibi çoğu durumda en büyük endişeniz olması gerektiğini düşünüyorum. Tonlarca dizeyi bir araya getirmedikçe veya daha düşük güçlü bir mobil cihaz için bir şey inşa etmedikçe, bu muhtemelen çalışma hızınızda bir çentik yapmaz.

Oldukça doğrusal bir şekilde dizeler oluşturduğum durumlarda, ya düz birleştirme ya da StringBuilder kullanarak en iyi seçenek olduğunu gördüm. Bunu, inşa ettiğiniz dizgenin çoğunun dinamik olduğu durumlarda öneririm. Metnin çok azı statik olduğundan, en önemli şey, gelecekte güncellenmesi gerektiğinde her bir dinamik metnin nereye konulduğu açıktır.

Öte yandan, içinde iki veya üç değişkenli büyük bir statik metin yığınından bahsediyorsanız, biraz daha az verimli olsa bile, dizeden kazandığınız netliği düşünüyorum. Bu haftanın başlarında, 4 sayfalık bir belgenin ortasına bir bit dinamik metin yerleştirmek zorunda kaldığımda kullandım. Tek parça halinde ise büyük metin yığınını birleştirmek, bir araya getirdiğiniz üç parçayı güncellemekten daha kolay olacaktır.


Evet! Dize etmek mantıklı olduğunda, yani dizeleri biçimlendirirken String.Format öğesini kullanın. Mekanik birleştirme yaparken dize birleştirme veya bir StringBuilder kullanın. Daima niyetinizi bir sonraki bakıcıya ileten yöntemi seçmeye çalışın.
Rob

8

Sadece string.Format tam olarak düşündüğünüzü yapmıyorsa, 6 yıl sonra Net45'teki testlerin tekrar çalışması.

Concat hala en hızlı ama gerçekten% 30'dan az fark. StringBuilder ve Format yaklaşık% 5-10 oranında farklılık gösterir. Testleri birkaç kez uygulayarak% 20 varyasyon aldım.

Milisaniye, bir milyon yineleme:

  • Zincirleme: 367
  • Her anahtar için yeni stringBuilder: 452
  • Önbellek Dizesi
  • dize. Biçim: 475

Aldığım ders, performans farkının önemsiz olması ve bu yüzden yapabileceğiniz en basit okunabilir kodu yazmanızı engellememesidir. Hangi benim para için genellikle ama her zaman değil 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);

2
"String.Format tam olarak düşündüğünüzü yapmaz" ile 4.5 kaynak kodunda önbelleğe alınmış bir StringBuilder örneği oluşturmaya ve yeniden kullanmaya çalıştığını kastediyorum. Bu yaklaşımı teste dahil ettim
Chris F Carroll

6

String.Format StringBuilderdahili olarak kullanır ... bu yüzden mantıksal olarak, daha fazla yük nedeniyle biraz daha az performans göstereceği fikrine yol açar. Bununla birlikte, basit bir dize birleştirmesi, bir dizeyi diğer ikisi arasına önemli bir derecede enjekte etmenin en hızlı yöntemidir. Bu kanıt, yıllar önce ilk Performans Testinde Rico Mariani tarafından kanıtlanmıştır. Basit gerçek şu ki, birleştirme ... dize parçalarının sayısı biliniyorsa (sınırlama olmadan ... bin parçayı birleştirebilirsiniz ... her zaman 1000 parçasını bildiğiniz sürece) ... her zaman daha hızlı StringBuilderveya Dize'dir. Biçim. Tek bir bellek tahsisi ve bir dizi bellek kopyasıyla gerçekleştirilebilirler. İşte kanıt

Ve sonuçta, FillStringChecked'i hafızayı kopyalamak için işaretçiler kullanan (Reflector ile çıkarılır) kullanan bazı String.Concat yöntemleri için gerçek kod:

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);
        }
    }
}

E sonra:

string what = "cat";
string inthehat = "The " + what + " in the hat!";

Zevk almak!


Net4, string.Format bir StringBuilder örneğini önbelleğe alır ve yeniden kullanır, böylece bazı kullanımlarda daha hızlı olabilir.
Chris F Carroll

3

Ayrıca, en hızlısı:

string cat = "cat";
string s = "The " + cat + " in the hat";

Hayır, dize birleştirme işlemi son derece yavaştır, çünkü .NET, dize değişkenleri arasında concat işlemleri arasında fazladan kopyalar oluşturur, bu durumda: iki fazladan kopya ve atamanın son kopyası. Sonuç: StringBuilderbu tür kodlamayı ilk etapta optimize etmek için yapılan performansa kıyasla son derece düşük performans .
Abel

Belki yazmak için en hızlı;)
UpTheCreek 13:10

2
@Abel: Cevabı ayrıntılardan yoksun olabilir, ancak bu yaklaşım bu özel örnekte en hızlı seçenektir. Derleyici bunu tek bir String.Concat () çağrısına dönüştürür, böylece bir StringBuilder ile değiştirmek aslında kodu yavaşlatır.
Dan C.

1
@Vaibhav doğrudur: bu durumda birleştirme en hızlısıdır. Tabii ki, defalarca defalarca tekrarlanmadığı veya belki de çok, çok daha büyük bir ip üzerinde çalıştırılmadığı sürece fark önemsiz olacaktır.
Ben Collins

0

Gerçekten bağlıdır. Birkaç birleşimi olan küçük dizeler için, aslında dizeleri eklemek daha hızlıdır.

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

Ancak daha büyük dize (çok çok büyük dizeler) için StringBuilder kullanmak daha verimlidir.


0

Yukarıdaki her iki durumda da, önceden tanımlanmış bir şablon dizesinin ortasına bir veya daha fazla dizgi enjekte etmek istiyorum.

Bu durumda, String.Format'ın en hızlı olduğunu öneririm çünkü tam olarak bu amaç için tasarımdır.



-1

String.Format, birleştirme için tasarlanmadığından, bir tarih gibi çeşitli girişlerin çıktısını biçimlendirmek için tasarlanmıştı.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.