Apakah String.Format seefisien StringBuilder

160

Misalkan saya memiliki stringbuilder di C # yang melakukan ini:

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

apakah itu seefisien atau lebih efisien daripada memiliki:

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

Jika demikian, mengapa?

EDIT

Setelah beberapa jawaban yang menarik, saya menyadari bahwa saya mungkin seharusnya sedikit lebih jelas dalam apa yang saya tanyakan. Saya tidak begitu banyak meminta yang lebih cepat menggabungkan string, tetapi yang lebih cepat dalam menyuntikkan satu string ke yang lain.

Dalam kedua kasus di atas saya ingin menyuntikkan satu atau lebih string ke tengah-tengah string template yang telah ditentukan.

Maaf bila membingungkan

lomaxx
sumber
Harap biarkan ini terbuka untuk memungkinkan peningkatan di masa mendatang.
Mark Biek
4
Dalam skenario kasus khusus, yang tercepat adalah keduanya: jika bagian yang akan diganti berukuran sama dengan bagian baru, Anda dapat mengubah string di tempat. Sayangnya, ini membutuhkan kode refleksi atau tidak aman dan sengaja melanggar keabadian string. Bukan praktik yang baik, tetapi jika kecepatan adalah masalah ... :)
Abel
dalam contoh yang diberikan di atas string s = "The "+cat+" in the hat";mungkin yang tercepat kecuali jika digunakan dalam satu lingkaran, dalam hal ini tercepat akan dengan StringBuilder inisialisasi di luar loop.
Surya Pratap

Jawaban:

146

CATATAN: Jawaban ini ditulis ketika .NET 2.0 adalah versi saat ini. Ini mungkin tidak lagi berlaku untuk versi yang lebih baru.

String.Formatmenggunakan StringBuilderinternal:

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

Kode di atas adalah potongan dari mscorlib, jadi pertanyaannya menjadi " StringBuilder.Append()lebih cepat dari StringBuilder.AppendFormat()"?

Tanpa benchmarking saya mungkin akan mengatakan bahwa contoh kode di atas akan berjalan lebih cepat menggunakan .Append(). Tapi ini dugaan, cobalah membuat tolok ukur dan / atau membuat profil keduanya untuk mendapatkan perbandingan yang tepat.

Orang ini, Jerry Dixon, melakukan pembandingan:

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

Diperbarui:

Sayangnya tautan di atas telah mati. Namun masih ada salinan di Way Back Machine:

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

Pada akhirnya, itu tergantung apakah pemformatan string Anda akan dipanggil berulang-ulang, yaitu Anda melakukan beberapa pemrosesan teks serius lebih dari 100 megabita teks, atau apakah itu dipanggil ketika pengguna mengklik tombol sekarang dan lagi. Kecuali jika Anda melakukan pekerjaan pemrosesan batch besar saya tetap menggunakan String.Format, ini membantu keterbacaan kode. Jika Anda mencurigai bott perfek maka tempel profiler pada kode Anda dan lihat di mana itu sebenarnya.

Kev
sumber
8
Satu masalah dengan benchmark pada halaman Jerry Dixon adalah bahwa dia tidak pernah menyebut .ToString()pada StringBuilderobjek. Lebih dari banyak iterasi, waktu itu membuat perbedaan besar, dan berarti dia tidak cukup membandingkan apel dengan apel. Itulah alasan dia menunjukkan kinerja yang luar biasa StringBuilderdan mungkin mengejutkannya. Saya hanya mengulangi tolok ukur yang memperbaiki kesalahan itu dan mendapatkan hasil yang diharapkan: String +operator itu tercepat, diikuti oleh StringBuilder, dengan String.Formatmengangkat bagian belakang.
Ben Collins
5
6 tahun kemudian, ini tidak lagi seperti itu. Di Net4, string.Format () membuat dan cache contoh StringBuilder yang digunakan kembali, sehingga dalam beberapa kasus uji mungkin lebih cepat daripada StringBuilder. Saya telah menempatkan tolok ukur yang direvisi dalam jawaban di bawah ini (yang masih mengatakan bahwa konser adalah yang tercepat dan untuk kasus pengujian saya, formatnya 10% lebih lambat daripada StringBuilder).
Chris F Carroll
45

Dari dokumentasi MSDN :

Kinerja operasi penggabungan untuk objek String atau StringBuilder tergantung pada seberapa sering alokasi memori terjadi. Operasi penggabungan String selalu mengalokasikan memori, sedangkan operasi penggabungan StringBuilder hanya mengalokasikan memori jika buffer objek StringBuilder terlalu kecil untuk mengakomodasi data baru. Akibatnya, kelas String lebih disukai untuk operasi gabungan jika jumlah objek String yang tetap digabungkan. Dalam hal itu, operasi gabungan individu bahkan dapat digabungkan menjadi satu operasi oleh kompiler. Objek StringBuilder lebih disukai untuk operasi penggabungan jika sejumlah string acak digabungkan; misalnya, jika sebuah loop menyatukan sejumlah string dari input pengguna.

Greg
sumber
12

Saya menjalankan beberapa tolok ukur kinerja cepat, dan untuk 100.000 operasi rata-rata lebih dari 10 kali berjalan, metode pertama (String Builder) mengambil hampir separuh waktu yang kedua (String Format).

Jadi, jika ini jarang terjadi, itu tidak masalah. Tetapi jika ini adalah operasi yang umum, maka Anda mungkin ingin menggunakan metode pertama.

Vaibhav
sumber
10

Saya harapkan String.Format lebih lambat - itu harus mengurai string dan kemudian menyatukannya.

Beberapa catatan:

  • Format adalah cara untuk mencari string yang terlihat oleh pengguna dalam aplikasi profesional; ini menghindari bug pelokalan
  • Jika Anda tahu panjang string yang dihasilkan sebelumnya, gunakan konstruktor StringBuilder (Int32) untuk menentukan kapasitas sebelumnya
McDowell
sumber
8

Saya pikir dalam banyak kasus seperti kejelasan ini, dan bukan efisiensi, harus menjadi perhatian terbesar Anda. Kecuali jika Anda menyatukan banyak string, atau membuat sesuatu untuk perangkat seluler berdaya rendah, ini mungkin tidak akan banyak mengurangi kecepatan lari Anda.

Saya telah menemukan bahwa, dalam kasus-kasus di mana saya membangun string dengan cara yang cukup linier, baik melakukan penggabungan lurus atau menggunakan StringBuilder adalah pilihan terbaik Anda. Saya menyarankan ini dalam kasus di mana sebagian besar string yang Anda buat adalah dinamis. Karena sangat sedikit teks yang statis, yang paling penting adalah jelas di mana setiap bagian teks dinamis dimasukkan jika perlu diperbarui di masa depan.

Di sisi lain, jika Anda berbicara tentang sebagian besar teks statis dengan dua atau tiga variabel di dalamnya, bahkan jika itu sedikit kurang efisien, saya pikir kejelasan yang Anda peroleh dari string. Saya menggunakan ini awal minggu ini ketika harus menempatkan satu bit teks dinamis di tengah dokumen 4 halaman. Akan lebih mudah untuk memperbarui potongan besar teks itu jika dalam satu bagian daripada harus memperbarui tiga bagian yang Anda gabungkan bersama.

saalon
sumber
Iya! Gunakan String.Format ketika masuk akal untuk melakukannya, yaitu ketika Anda memformat string. Gunakan penggabungan string atau StringBuilder saat Anda melakukan penggabungan mekanis. Selalu berusaha untuk memilih metode yang mengomunikasikan niat Anda ke pengelola berikutnya.
Rob
8

Jika hanya karena string. Kebiasaan tidak persis melakukan apa yang Anda pikirkan, berikut ini adalah ulangan dari tes 6 tahun kemudian di Net45.

Concat masih tercepat tetapi sebenarnya perbedaannya kurang dari 30%. StringBuilder dan Format berbeda hampir 5-10%. Saya mendapat variasi 20% menjalankan tes beberapa kali.

Milidetik, sejuta iterasi:

  • Rangkai: 367
  • StringBuilder baru untuk setiap kunci: 452
  • StringBuilder dalam cache: 419
  • string.Format: 475

Pelajaran yang saya ambil adalah bahwa perbedaan kinerja itu sepele dan karena itu seharusnya tidak menghentikan Anda menulis kode yang paling mudah dibaca yang Anda bisa. Yang untuk uang saya sering tetapi tidak selalu 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);
Chris F Carroll
sumber
2
Dengan "string.Format tidak persis melakukan apa yang Anda pikirkan" Maksud saya dalam kode sumber 4,5 mencoba untuk membuat dan menggunakan kembali contoh StringBuilder yang di-cache. Jadi saya memasukkan pendekatan itu dalam ujian
Chris F Carroll
6

String.Format menggunakan StringBuilderinternal ... jadi secara logis yang mengarah pada gagasan bahwa itu akan menjadi sedikit kurang berkinerja karena lebih banyak overhead. Namun, penggabungan string sederhana adalah metode tercepat untuk menyuntikkan satu string antara dua string lainnya ... dengan tingkat yang signifikan. Bukti ini ditunjukkan oleh Rico Mariani dalam Kuis Kinerja pertamanya, tahun lalu. Fakta sederhananya adalah penggabungan ... ketika jumlah bagian string diketahui (tanpa batasan ... Anda dapat menggabungkan seribu bagian ... selama Anda tahu selalu 1000 bagian) ... selalu lebih cepat daripada StringBuilderatau String. Format. Mereka dapat dilakukan dengan alokasi memori tunggal dan serangkaian salinan memori. Ini buktinya

Dan di sini adalah kode aktual untuk beberapa metode String.Compat, yang pada akhirnya memanggil FillStringChecked yang menggunakan pointer untuk menyalin memori (diekstraksi melalui 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);
        }
    }
}

Sehingga kemudian:

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

Nikmati!

jrista
sumber
di Net4, string.Format cache dan menggunakan kembali contoh StringBuilder sehingga dalam beberapa penggunaan mungkin lebih cepat.
Chris F Carroll
3

Oh juga, yang tercepat adalah:

string cat = "cat";
string s = "The " + cat + " in the hat";
Vaibhav
sumber
tidak, penggabungan string sangat lambat, karena .NET membuat salinan tambahan variabel string Anda di antara operasi concat, dalam hal ini: dua salinan tambahan ditambah salinan akhir untuk penugasan. Hasil: kinerja sangat buruk dibandingkan StringBuilderyang dibuat untuk mengoptimalkan jenis pengkodean ini di tempat pertama.
Abel
Paling cepat mengetik mungkin;)
UpTheCreek
2
@ Bel: Jawabannya mungkin kurang detail, tetapi pendekatan ini adalah pilihan tercepat, dalam contoh khusus ini. Compiler akan mengubah ini menjadi panggilan String.Concat () tunggal, jadi mengganti dengan StringBuilder sebenarnya akan memperlambat kode.
Dan C.
1
@Vaibhav benar: dalam hal ini, penggabungan adalah yang tercepat. Tentu saja, perbedaannya tidak akan signifikan kecuali diulang berkali-kali, atau mungkin dioperasikan pada string yang jauh lebih besar.
Ben Collins
0

Itu sangat tergantung. Untuk string kecil dengan beberapa rangkaian, itu sebenarnya lebih cepat hanya untuk menambahkan string.

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

Tetapi untuk string yang lebih besar (string yang sangat sangat besar), maka lebih efisien untuk menggunakan StringBuilder.

Joseph Daigle
sumber
0

Dalam kedua kasus di atas saya ingin menyuntikkan satu atau lebih string ke tengah-tengah string template yang telah ditentukan.

Dalam hal ini, saya akan menyarankan String.Format adalah yang tercepat karena dirancang untuk tujuan yang tepat.

GateKiller
sumber
-1

Saya tidak menyarankan, karena String.Format tidak dirancang untuk digabungkan, itu dirancang untuk memformat output dari berbagai input seperti tanggal.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
GateKiller
sumber