เป็น String.Format ที่มีประสิทธิภาพเท่ากับ StringBuilder


160

สมมติว่าฉันมีตัวสร้างสตริงใน C # ที่ทำสิ่งนี้:

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

จะเป็นอย่างมีประสิทธิภาพหรือมีประสิทธิภาพมากขึ้นเช่นเดียวกับที่มี:

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

ถ้าเป็นเช่นนั้นทำไม

แก้ไข

หลังจากคำตอบที่น่าสนใจฉันรู้ว่าฉันน่าจะชัดเจนขึ้นเล็กน้อยในสิ่งที่ฉันขอ ฉันไม่ได้มากขอซึ่งเป็นเร็วที่เชื่อมโยงสตริง แต่ที่เร็วที่ฉีดหนึ่งสายเข้าไปอีก

ในทั้งสองกรณีข้างต้นฉันต้องการแทรกหนึ่งสตริงขึ้นไปในกึ่งกลางของสตริงแม่แบบที่กำหนดไว้ล่วงหน้า

ขอโทษสำหรับความสับสน


โปรดเปิดไว้เพื่อเปิดโอกาสให้มีการปรับปรุงในอนาคต
Mark Biek

4
ในสถานการณ์พิเศษกรณีที่เร็วที่สุดไม่ใช่ของเหล่านี้ถ้าส่วนที่จะเปลี่ยนมีขนาดเท่ากับชิ้นส่วนใหม่คุณสามารถเปลี่ยนสตริงแบบแทนที่ได้ น่าเสียดายที่ต้องมีการสะท้อนหรือรหัสที่ไม่ปลอดภัยและจงใจละเมิดความไม่เปลี่ยนแปลงของสตริง ไม่ใช่วิธีที่ดี แต่ถ้าความเร็วเป็นปัญหา ... :)
อาเบล

ในตัวอย่างที่ระบุข้างต้นstring s = "The "+cat+" in the hat";อาจเร็วที่สุดยกเว้นว่าจะใช้ในการวนซ้ำซึ่งในกรณีนี้เร็วที่สุดจะเป็นการStringBuilder เริ่มต้นนอกลูป
Surya Pratap

คำตอบ:


146

หมายเหตุ:คำตอบนี้ถูกเขียนขึ้นเมื่อ. NET 2.0 เป็นรุ่นปัจจุบัน สิ่งนี้อาจใช้ไม่ได้กับรุ่นที่ใหม่กว่า

String.Formatใช้StringBuilderภายใน:

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

โค้ดด้านบนนี้เป็นข้อมูลโค้ดจาก mscorlib ดังนั้นคำถามจะกลายเป็น " StringBuilder.Append()เร็วกว่าStringBuilder.AppendFormat()" หรือไม่

.Append()โดยไม่ต้องเปรียบเทียบผมอาจจะพูดได้ว่าโค้ดตัวอย่างข้างต้นจะทำงานได้อย่างรวดเร็วโดยใช้ แต่เป็นการเดาลองเปรียบเทียบและ / หรือทำโปรไฟล์ทั้งสองเพื่อรับการเปรียบเทียบที่เหมาะสม

เด็กชายคนนี้เจอร์รี่ดิกซันได้ทำการเปรียบเทียบ:

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

Updated:

น่าเศร้าที่ลิงค์ด้านบนเสียชีวิต อย่างไรก็ตามยังมีสำเนาบนเครื่อง Way Back:

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

ในตอนท้ายของวันขึ้นอยู่กับว่าการจัดรูปแบบสตริงของคุณจะถูกเรียกว่าซ้ำ ๆ หรือไม่นั่นคือคุณกำลังทำการประมวลผลข้อความที่ร้ายแรงมากกว่า 100 ข้อความเมกะไบต์หรือไม่ว่าจะถูกเรียกเมื่อผู้ใช้คลิกปุ่มตอนนี้และอีกครั้ง เว้นแต่ว่าคุณกำลังทำหน้าที่ประมวลผลชุดใหญ่ที่ฉันติดกับ String.Format มันจะช่วยให้สามารถอ่านรหัสได้ หากคุณสงสัยว่าคอขวดนั้นติดที่ profiler ในรหัสของคุณและดูว่ามันอยู่ที่ไหน


8
ปัญหาอย่างหนึ่งของการวัดประสิทธิภาพบนหน้าเจอร์รีดิกซันก็คือเขาไม่เคยโทร.ToString()หาStringBuilderวัตถุ การทำซ้ำหลายครั้งยิ่งใหญ่ในเวลานั้นสร้างความแตกต่างอย่างมากและหมายความว่าเขาไม่ได้เปรียบเทียบแอปเปิ้ลกับแอปเปิ้ลค่อนข้างมาก นั่นเป็นเหตุผลที่เขาแสดงให้เห็นถึงประสิทธิภาพที่ยอดเยี่ยมStringBuilderและน่าประหลาดใจสำหรับเขา ฉันเพิ่งทำซ้ำมาตรฐานแก้ไขข้อผิดพลาดนั้นและได้รับผลลัพธ์ที่คาดไว้: String +ผู้ประกอบการนั้นเร็วที่สุดตามStringBuilderด้วยString.Formatการนำขึ้นด้านหลัง
เบ็นคอลลินส์

5
6 ปีต่อมามันไม่ได้เป็นเช่นนี้อีกต่อไปแล้ว ใน Net4, string.Format () สร้างและแคชอินสแตนซ์ StringBuilder ที่มันนำมาใช้ใหม่ดังนั้นในบางกรณีทดสอบอาจจะเร็วกว่า StringBuilder ฉันวางมาตรฐานที่แก้ไขแล้วในคำตอบด้านล่าง (ซึ่งยังบอกว่า concat นั้นเร็วที่สุดและสำหรับกรณีทดสอบของฉันรูปแบบจะช้ากว่า StringBuilder 10%)
Chris F Carroll

45

จากเอกสาร MSDN :

ประสิทธิภาพของการดำเนินการเชื่อมต่อสำหรับวัตถุ String หรือ StringBuilder ขึ้นอยู่กับความถี่ในการจัดสรรหน่วยความจำที่เกิดขึ้น การดำเนินการเชื่อมต่อสตริงจะจัดสรรหน่วยความจำเสมอในขณะที่การดำเนินการเชื่อมต่อแบบ StringBuilder จะจัดสรรหน่วยความจำเฉพาะถ้าบัฟเฟอร์วัตถุ StringBuilder มีขนาดเล็กเกินไปที่จะรองรับข้อมูลใหม่ ดังนั้นคลาส String จึงเหมาะสำหรับการดำเนินการเรียงต่อกันหากมีการต่อจำนวนคงที่ของวัตถุสตริง ในกรณีดังกล่าวการดำเนินการเรียงต่อกันอาจรวมกันเป็นการดำเนินการเดียวโดยคอมไพเลอร์ วัตถุ StringBuilder จะดีกว่าสำหรับการดำเนินการเชื่อมต่อถ้าจำนวนสตริงโดยพลการจะถูกตัดแบ่ง ตัวอย่างเช่นถ้าวงวนต่อกันเป็นจำนวนสุ่มของสตริงของการป้อนข้อมูลของผู้ใช้


12

ฉันใช้การวัดประสิทธิภาพอย่างรวดเร็วและสำหรับการดำเนินการ 100,000 ครั้งโดยเฉลี่ยมากกว่า 10 ครั้งวิธีแรก (ตัวสร้างสตริง) ใช้เวลาเกือบครึ่งหนึ่งของวินาที (รูปแบบสตริง)

ดังนั้นหากนี่ไม่บ่อยนักมันก็ไม่สำคัญ แต่ถ้าเป็นการดำเนินการทั่วไปคุณอาจต้องการใช้วิธีแรก


10

ผมจะคาดหวังString.Formatได้ช้า - มันมีการแยกสตริงและแล้ว concatenate มัน

คู่ของบันทึกย่อ:

  • รูปแบบเป็นวิธีที่จะไปสำหรับสตริงที่ผู้ใช้มองเห็นได้ในการใช้งานระดับมืออาชีพ; เพื่อหลีกเลี่ยงบั๊กการโลคัลไลเซชัน
  • หากคุณทราบความยาวของสตริงผลลัพธ์ก่อนหน้าให้ใช้ตัวสร้างStringBuilder (Int32)เพื่อกำหนดความจุล่วงหน้า

8

ฉันคิดว่าในกรณีส่วนใหญ่เช่นความชัดเจนนี้และไม่ใช่ประสิทธิภาพควรเป็นความกังวลที่ใหญ่ที่สุดของคุณ หากคุณไม่ได้รวมสตริงจำนวนมากเข้าด้วยกันหรือสร้างบางสิ่งบางอย่างสำหรับอุปกรณ์มือถือที่มีพลังงานต่ำกว่านี้อาจไม่ทำให้คุณต้องวิ่งเร็วนัก

ฉันได้พบว่าในกรณีที่ฉันกำลังสร้างสตริงในแบบเชิงเส้นไม่ว่าจะทำการเรียงต่อกันแบบตรงหรือใช้ StringBuilder เป็นตัวเลือกที่ดีที่สุดของคุณ ฉันแนะนำสิ่งนี้ในกรณีที่สตริงส่วนใหญ่ที่คุณกำลังสร้างเป็นแบบไดนามิก เนื่องจากข้อความมีน้อยมากคงที่สิ่งที่สำคัญที่สุดคือชัดเจนว่ามีการใส่ข้อความไดนามิกไว้ในกรณีที่จำเป็นต้องอัปเดตในอนาคต

ในทางกลับกันถ้าคุณกำลังพูดถึงข้อความขนาดใหญ่ที่มีตัวแปรสองหรือสามตัวถึงแม้ว่ามันจะมีประสิทธิภาพน้อยกว่าเล็กน้อยฉันคิดว่าความชัดเจนที่คุณได้รับจากสตริงรูปแบบที่ทำให้มันคุ้มค่า ฉันใช้สิ่งนี้เมื่อต้นสัปดาห์นี้เมื่อต้องวางข้อความไดนามิกหนึ่งบิตที่กึ่งกลางของเอกสาร 4 หน้า มันจะง่ายกว่าในการอัพเดทอันยิ่งใหญ่ของข้อความถ้ามันเป็นชิ้นเดียวแทนที่จะต้องอัพเดทสามชิ้นที่คุณต่อกันเข้าด้วยกัน


ใช่ ใช้ String.Format เมื่อเหมาะสมเช่นเมื่อคุณกำลังจัดรูปแบบสตริง ใช้การต่อสตริงหรือ StringBuilder เมื่อคุณทำการเชื่อมต่อเชิงกล พยายามเลือกวิธีที่สื่อสารความตั้งใจของคุณกับผู้ดูแลคนต่อไปเสมอ
Rob

8

ถ้าเพียงเพราะ string รูปแบบที่ไม่ทำสิ่งที่คุณอาจคิดว่านี่คือการทดสอบอีกครั้ง 6 ปีต่อมาใน Net45

Concat ยังเร็วที่สุด แต่จริงๆแล้วมันต่างกันน้อยกว่า 30% StringBuilder และรูปแบบแตกต่างกันแทบ 5-10% ฉันได้รับการทดสอบ 20% จากการทดสอบสองสามครั้ง

มิลลิวินาทีการแสดงซ้ำล้านครั้ง:

  • การต่อเรียง: 367
  • ใหม่ stringBuilder สำหรับแต่ละคีย์: 452
  • StringBuilder ที่แคชไว้: 419
  • string รูปแบบ: 475

บทเรียนที่ฉันนำมาใช้คือความแตกต่างของประสิทธิภาพนั้นไม่สำคัญดังนั้นจึงไม่ควรหยุดที่คุณจะเขียนโค้ดที่อ่านง่ายที่สุดที่คุณสามารถทำได้ ซึ่งสำหรับเงินของฉันมักจะเป็น 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 ไม่ได้ทำสิ่งที่คุณอาจคิดว่า" ฉันหมายความว่าในซอร์สโค้ด 4.5 มันพยายามที่จะสร้างและใช้อินสแตนซ์ StringBuilder ที่แคชอีกครั้ง ดังนั้นฉันจึงรวมวิธีการนั้นไว้ในการทดสอบ
Chris F Carroll

6

String.Format ใช้StringBuilderภายใน ... ดังนั้นเหตุผลที่นำไปสู่ความคิดที่ว่ามันจะมีประสิทธิภาพน้อยกว่าเนื่องจากค่าใช้จ่ายเพิ่มเติม อย่างไรก็ตามการต่อสายอย่างง่ายเป็นวิธีที่เร็วที่สุดในการฉีดหนึ่งสายระหว่างสองสาย ... โดยระดับที่สำคัญ หลักฐานนี้แสดงให้เห็นโดย Rico Mariani ในแบบทดสอบประสิทธิภาพชุดแรกของเขาเมื่อหลายปีก่อน ความจริงง่ายๆคือ concatenations ... เมื่อจำนวนส่วนสตริงเป็นที่รู้จัก (ไม่ จำกัด .. คุณสามารถต่อกันพันส่วน ... ตราบใดที่คุณรู้เสมอ 1,000 ส่วน) ... เร็วกว่าStringBuilderหรือสตริง รูปแบบ. พวกเขาสามารถทำได้ด้วยการจัดสรรหน่วยความจำเดียวชุดของสำเนาหน่วยความจำ นี่คือหลักฐาน

และนี่คือรหัสที่แท้จริงสำหรับวิธีการบางอย่างของ String.Concat ซึ่งในที่สุดเรียก FillStringChecked ซึ่งใช้พอยน์เตอร์เพื่อคัดลอกหน่วยความจำ (แยกผ่านตัวสะท้อน):

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

ดังนั้น:

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

สนุก!


ใน Net4 สตริง string.Format และนำอินสแตนซ์ของ StringBuilder กลับมาใช้ใหม่ดังนั้นในบางประเพณีอาจเร็วขึ้น
Chris F Carroll

3

โอ้ด้วยวิธีที่เร็วที่สุดคือ:

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

ไม่การต่อสตริงจะช้ามากเนื่องจาก. NET สร้างสำเนาพิเศษของตัวแปรสตริงของคุณระหว่างการดำเนินการเชื่อมต่อในกรณีนี้: สองสำเนาเพิ่มเติมรวมถึงสำเนาสุดท้ายสำหรับการมอบหมาย ผลลัพธ์: ประสิทธิภาพต่ำมากเมื่อเทียบกับStringBuilderการปรับการเข้ารหัสประเภทนี้ตั้งแต่แรก
Abel

เร็วที่สุดในการพิมพ์อาจจะ;)
UpTheCreek

2
@Abel: คำตอบอาจขาดรายละเอียด แต่วิธีการนี้เป็นตัวเลือกที่เร็วที่สุดในตัวอย่างนี้ คอมไพเลอร์จะแปลงสิ่งนี้เป็นการโทรแบบ String.Concat () เดียวดังนั้นการแทนที่ด้วย StringBuilder จะทำให้โค้ดช้าลง
Dan C.

1
@Vaibhav ถูกต้อง: ในกรณีนี้การต่อข้อมูลจะเร็วที่สุด แน่นอนความแตกต่างจะไม่มีนัยสำคัญเว้นแต่ทำซ้ำหลายครั้งมากหรืออาจดำเนินการผ่านสายอักขระที่มีขนาดใหญ่กว่ามาก
เบ็นคอลลินส์

0

มันขึ้นอยู่กับ สำหรับสตริงขนาดเล็กที่มีการต่อกันไม่กี่อันที่จริงมันเร็วกว่าเพียงต่อท้ายสตริง

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

แต่สำหรับสตริงที่มีขนาดใหญ่กว่า (สตริงที่มีขนาดใหญ่มาก) ก็จะมีประสิทธิภาพมากขึ้นในการใช้ StringBuilder


0

ในทั้งสองกรณีข้างต้นฉันต้องการแทรกหนึ่งสตริงขึ้นไปในกึ่งกลางของสตริงแม่แบบที่กำหนดไว้ล่วงหน้า

ในกรณีนี้ฉันขอแนะนำ String รูปแบบที่เร็วที่สุดเพราะมันถูกออกแบบมาเพื่อวัตถุประสงค์ที่แน่นอน



-1

ฉันจะแนะนำไม่ได้เนื่องจาก String.Format ไม่ได้ออกแบบมาสำหรับการต่อกันมันเป็นการออกแบบสำหรับการจัดรูปแบบเอาต์พุตของอินพุตต่างๆเช่นวันที่

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.