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

यदि हां, तो क्यों?

संपादित करें

कुछ दिलचस्प जवाबों के बाद, मुझे एहसास हुआ कि जो मैं पूछ रहा था उसमें मुझे शायद थोड़ा स्पष्ट होना चाहिए था। मैं इतना नहीं पूछ रहा था कि कौन सा एक स्ट्रिंग को सुगम बनाने में तेज था, लेकिन जो एक स्ट्रिंग को दूसरे में इंजेक्ट करने में तेज है ।

ऊपर के दोनों मामलों में मैं एक या अधिक स्ट्रिंग्स को पूर्वनिर्धारित टेम्पलेट स्ट्रिंग के बीच में इंजेक्ट करना चाहता हूं।

गलतफहमी के लिए खेद है


भविष्य के सुधार की अनुमति देने के लिए कृपया इन्हें खुला छोड़ दें।
मार्क बीसेक

4
विशेष स्थिति में, क्विकेस्ट इनमें से कुछ भी नहीं है: यदि प्रतिस्थापित किया जाने वाला भाग नए भाग के आकार के बराबर है, तो आप स्ट्रिंग को जगह में बदल सकते हैं। दुर्भाग्य से, इसके लिए प्रतिबिंब या असुरक्षित कोड की आवश्यकता होती है और जानबूझकर स्ट्रिंग की अपरिवर्तनीयता का उल्लंघन होता है। एक अच्छा अभ्यास नहीं है, लेकिन अगर गति एक मुद्दा है ... :)
हाबिल

ऊपर दिए गए उदाहरण में string s = "The "+cat+" in the hat";यह सबसे तेज हो सकता है जब तक कि इसे लूप में उपयोग नहीं किया जाता है, इस मामले में सबसे तेज StringBuilder लूप के बाहर एक इनिशियलाइज़ के साथ होगा ।
सूर्य प्रताप

जवाबों:


146

नोट: यह उत्तर तब लिखा गया था जब .NET 2.0 वर्तमान संस्करण था। यह अब बाद के संस्करणों पर लागू नहीं हो सकता है।

String.FormatStringBuilderआंतरिक रूप से उपयोग करता है :

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

अपडेट किया गया:

अफसोस की बात है कि ऊपर दिए गए लिंक की मृत्यु हो गई है। हालाँकि अभी भी वे बैक मशीन पर एक प्रति है:

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

दिन के अंत में यह निर्भर करता है कि क्या आपके स्ट्रिंग फॉर्मेटिंग को दोहराव से कहा जा रहा है, यानी आप 100 मेगाबाइट के टेक्स्ट पर कुछ गंभीर टेक्स्ट प्रोसेसिंग कर रहे हैं, या यह तब कहा जा रहा है जब उपयोगकर्ता अब और फिर से एक बटन क्लिक करता है। जब तक आप कुछ बड़ा बैच प्रसंस्करण कार्य नहीं कर रहे हैं, मैं String.Format के साथ रहना चाहता हूँ, यह कोड पठनीयता को प्रभावित करता है। यदि आपको एक परिपूर्ण अड़चन पर संदेह है, तो अपने कोड पर एक प्रोफाइलर चिपकाएं और देखें कि यह वास्तव में कहां है।


8
जेरी डिक्सन के पेज पर बेंचमार्क के साथ एक समस्या यह है कि वह कभी .ToString()भी StringBuilderऑब्जेक्ट पर कॉल नहीं करता है । एक महान कई पुनरावृत्तियों पर, वह समय एक बड़ा अंतर बनाता है, और इसका मतलब है कि वह सेब के लिए सेब की तुलना नहीं कर रहा है। यही कारण है कि वह इस तरह के शानदार प्रदर्शन को दर्शाता है StringBuilderऔर शायद अपने आश्चर्य के लिए खाता है। मैं तो बस बेंचमार्क उस गलती को सही करने दोहराया और अपेक्षित परिणाम मिल गया: String +ऑपरेटर था सबसे तेज, जिसके बाद StringBuilder, साथ String.Formatरियर परवरिश।
बेन कॉलिन्स

5
6 साल बाद, यह अब और नहीं है। Net4 में string.Format () एक StringBuilder उदाहरण बनाता है और कैश करता है जिसे वह पुन: उपयोग करता है, इसलिए यह कुछ परीक्षण मामलों में StringBuilder की तुलना में तेज़ हो सकता है। मैंने नीचे दिए गए उत्तर में एक संशोधित बेंचमार्क लगाया है (जो अभी भी कहता है कि कॉनकैट सबसे तेज़ है और मेरे परीक्षण के मामले के लिए, स्ट्रिंग स्ट्रिंगब्यूरल की तुलना में 10% धीमा है)।
क्रिस एफ कैरोल

45

से MSDN प्रलेखीकरण :

किसी स्ट्रिंग या StringBuilder ऑब्जेक्ट के लिए एक कॉनएनेशन ऑपरेशन का प्रदर्शन इस बात पर निर्भर करता है कि मेमोरी एलोकेशन कितनी बार होता है। एक स्ट्रिंग संयोजन ऑपरेशन हमेशा मेमोरी आवंटित करता है, जबकि एक स्ट्रिंगबर्बले कॉन्टेक्नेशन ऑपरेशन केवल मेमोरी आवंटित करता है यदि नए डेटा को समायोजित करने के लिए StringBuilder ऑब्जेक्ट बफर बहुत छोटा है। नतीजतन, एक स्थिर संख्या के लिए स्ट्रिंग क्लास बेहतर है यदि स्ट्रिंग ऑब्जेक्ट्स की एक निश्चित संख्या को मिलाया जाता है। उस स्थिति में, संकलक द्वारा अलग-अलग संघनन के संचालन को एकल ऑपरेशन में भी जोड़ा जा सकता है। एक स्ट्रिंग ऑपरेशन के लिए स्ट्रिंग स्ट्रिंग ऑब्जेक्ट बेहतर होता है, यदि स्ट्रिंग्स की एक मनमानी संख्या समाप्‍त हो जाती है; उदाहरण के लिए, यदि एक लूप उपयोगकर्ता इनपुट के एक यादृच्छिक संख्या को समेटता है।


12

मैंने कुछ त्वरित प्रदर्शन बेंचमार्क चलाए, और 10 से अधिक रन के लिए औसतन 100,000 संचालन के लिए, पहली विधि (स्ट्रिंग बिल्डर) दूसरे (स्ट्रिंग प्रारूप) के लगभग आधे समय लेती है।

इसलिए, अगर यह निराला है, तो इससे कोई फर्क नहीं पड़ता। लेकिन अगर यह एक सामान्य ऑपरेशन है, तो आप पहली विधि का उपयोग करना चाह सकते हैं।


10

मैं String.Format के धीमे होने की उम्मीद करूंगा - इसे स्ट्रिंग को पार्स करना होगा और फिर इसे बदलना होगा।

नोटों की जोड़ी:

  • प्रारूप पेशेवर अनुप्रयोगों में उपयोगकर्ता-दृश्यमान तार के लिए जाने का तरीका है; यह स्थानीयकरण कीड़े से बचा जाता है
  • यदि आप परिणामी स्ट्रिंग की लंबाई पहले से जानते हैं, तो क्षमता को पूर्वनिर्धारित करने के लिए StringBuilder (Int32) कंस्ट्रक्टर का उपयोग करें

8

मुझे लगता है कि इस स्पष्टता और दक्षता नहीं जैसे ज्यादातर मामलों में, आपकी सबसे बड़ी चिंता होनी चाहिए। जब तक आप एक साथ टन के तारों को कुचल नहीं रहे हैं, या कम शक्ति वाले मोबाइल डिवाइस के लिए कुछ का निर्माण कर रहे हैं, यह संभवतः आपकी रन गति में बहुत अधिक सेंध नहीं लगाएगा।

मैंने पाया है कि, ऐसे मामलों में जहां मैं काफी रैखिक फैशन में स्ट्रिंग्स का निर्माण कर रहा हूं, या तो सीधे कॉन्टेक्शंस कर रहा हूं या स्ट्रिंगबर्ल का उपयोग करना आपका सबसे अच्छा विकल्प है। मैं उन मामलों में यह सुझाव देता हूं जहां आपके द्वारा बनाए जा रहे स्ट्रिंग का अधिकांश हिस्सा गतिशील है। चूंकि बहुत कम पाठ स्थिर है, इसलिए सबसे महत्वपूर्ण बात यह है कि यह स्पष्ट है कि भविष्य में अद्यतन किए जाने की स्थिति में गतिशील पाठ के प्रत्येक टुकड़े को कहां रखा जा रहा है।

दूसरी ओर, यदि आप स्थैतिक पाठ के एक बड़े हिस्से के बारे में बात कर रहे हैं, जिसमें दो या तीन चर हैं, भले ही यह थोड़ा कम कुशल हो, मुझे लगता है कि आप स्ट्रिंग से जो स्पष्टता प्राप्त करते हैं। वह इसे योग्य बनाता है। मैंने इस सप्ताह के शुरू में 4 पेज के दस्तावेज के केंद्र में एक बिट डायनामिक टेक्स्ट रखा था। पाठ के उस बड़े हिस्से को अद्यतन करना आसान होगा यदि एक टुकड़े में तीन टुकड़ों को अद्यतन करने की अपेक्षा आप एक साथ सम्‍मिलित करते हैं।


हाँ! String.Format का उपयोग करें जब यह ऐसा करने के लिए समझ में आता है, यानी जब आप तार को स्वरूपित कर रहे हैं। जब आप यांत्रिक संघनन कर रहे हों, तो स्ट्रिंग समाकलन या स्ट्रिंग स्ट्रिंग का उपयोग करें। हमेशा उस विधि को लेने का प्रयास करें जो आपके इरादे को अगले अनुरक्षक तक पहुंचाती है।
रोब

8

अगर केवल स्ट्रिंग की वजह से हो सकता है। फरमाट वही नहीं करता है जो आप सोच सकते हैं, तो यहां नेट 45 पर 6 साल बाद परीक्षणों का एक पुनर्मिलन है।

कॉनैट अभी भी सबसे तेज़ है लेकिन वास्तव में यह 30% से कम अंतर है। StringBuilder और प्रारूप मुश्किल से 5-10% से भिन्न होते हैं। मुझे कई बार परीक्षणों को चलाने में 20% की विविधताएं मिलीं।

मिलीसेकंड, एक मिलियन पुनरावृत्तियों:

  • संघनन: 367
  • प्रत्येक कुंजी के लिए नया stringBuilder: 452
  • कैश्ड स्ट्रिंगबर्स्ट: 419
  • string.Format: 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 स्रोत कोड में यह एक कैश्ड स्ट्रिंगब्यूलर इंस्टेंस को बनाने और फिर से उपयोग करने की कोशिश करता है। इसलिए मैंने परीक्षण में उस दृष्टिकोण को शामिल किया
क्रिस एफ कैरोल

6

String.Format StringBuilderआंतरिक रूप से उपयोग करता है ... इसलिए तार्किक रूप से इस विचार की ओर जाता है कि यह अधिक उपरि के कारण थोड़ा कम प्रदर्शन करने वाला होगा। हालांकि, एक साधारण स्ट्रिंग का संघनन एक स्ट्रिंग को दो अन्य के बीच इंजेक्ट करने का सबसे तेज़ तरीका है ... एक महत्वपूर्ण डिग्री द्वारा। इस साक्ष्य का प्रदर्शन रिको मारियानी ने अपने पहले प्रदर्शन क्विज़ में वर्षों पहले किया था। सरल तथ्य यह है कि संघनन ... जब स्ट्रिंग भागों की संख्या ज्ञात होती है (बिना किसी सीमा के..आप एक हजार भागों को मिला सकते हैं ... जब तक आप इसके 1000 भागों को जानते हैं) ... हमेशा की तुलना में 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 उदाहरण का पुन: उपयोग करता है ताकि कुछ उपयोग तेजी से हो सकें।
क्रिस एफ कैरोल

3

ओह, सबसे तेज़ होगा:

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

नहीं, स्ट्रिंग का संघनन अत्यंत धीमा है, क्योंकि .NET इस प्रक्रिया में आपके स्ट्रिंग चर की अतिरिक्त प्रतियां बनाता है, इस मामले में: दो अतिरिक्त प्रतियां और असाइनमेंट के लिए अंतिम प्रतिलिपि। परिणाम: इसकी तुलना में बेहद खराब प्रदर्शन StringBuilderपहली जगह में इस प्रकार के कोडिंग को अनुकूलित करने के लिए किया गया है।
हाबिल

); सबसे तेजी से टाइप करने के लिए हो सकता है
UpTheCreek

2
@ विश्वास: उत्तर में विवरण की कमी हो सकती है, लेकिन यह दृष्टिकोण इस विशेष उदाहरण में सबसे तेज़ विकल्प है। कंपाइलर इसे एक सिंगल स्ट्रिंग.कॉन्कट () कॉल में बदल देगा, इसलिए एक स्ट्रिंगबर्ल के साथ प्रतिस्थापित करने से वास्तव में कोड धीमा हो जाएगा।
डेन सी।

1
@ वैभव सही है: इस मामले में, सहमति सबसे तेज़ है। बेशक, अंतर तब तक महत्वहीन होगा जब तक कि कई बार एक महान दोहराया नहीं जाता है, या शायद बहुत अधिक, बहुत बड़ा स्ट्रिंग पर संचालित होता है।
बेन कॉलिन्स

0

यह वास्तव में निर्भर करता है। कुछ संगतों के साथ छोटे तार के लिए, यह वास्तव में सिर्फ तार जोड़ने के लिए तेज़ है।

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

लेकिन बड़ी स्ट्रिंग (बहुत बड़े तार) के लिए, यह स्ट्रिंगब्यूबर्ल का उपयोग करने के लिए अधिक कुशल है।


0

ऊपर के दोनों मामलों में मैं एक या अधिक स्ट्रिंग्स को पूर्वनिर्धारित टेम्पलेट स्ट्रिंग के बीच में इंजेक्ट करना चाहता हूं।

किस मामले में, मैं सुझाव दूंगा कि String.Format सबसे तेज़ है क्योंकि यह उस सटीक उद्देश्य के लिए डिज़ाइन है।



-1

मैं सुझाव नहीं दूंगा, क्योंकि स्ट्रिंग.फोरमैट को संगति के लिए डिज़ाइन नहीं किया गया था, यह एक तिथि के रूप में विभिन्न इनपुट के आउटपुट को स्वरूपित करने के लिए डिज़ाइन किया गया था।

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.