سؤال

لنفترض أن لدي مُنشئ سلسلة في 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);

إذا كان الأمر كذلك لماذا؟

يحرر

بعد بعض الإجابات المثيرة للاهتمام، أدركت أنه ربما كان ينبغي علي أن أكون أكثر وضوحًا فيما كنت أطلبه.لم أكن أسأل كثيرًا عن أيهما أسرع في سلسلة سلسلة، ولكن أيهما أسرع في حقن سلسلة واحدة إلى أخرى.

في كلتا الحالتين أعلاه أريد إدخال سلسلة واحدة أو أكثر في منتصف سلسلة قالب محددة مسبقًا.

اسف لخلط الامور

هل كانت مفيدة؟

المحلول

ملحوظة: تمت كتابة هذه الإجابة عندما كان .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، لذلك يصبح السؤال "is StringBuilder.Append() اسرع من StringBuilder.AppendFormat()"?

بدون قياس الأداء، ربما أقول إن نموذج التعليمات البرمجية أعلاه سيتم تشغيله بسرعة أكبر باستخدام .Append().ولكن هذا مجرد تخمين، حاول قياس الأداء و/أو تصنيف الاثنين للحصول على مقارنة مناسبة.

هذا الفصل، جيري ديكسون، قام ببعض المعايير:

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

محدث:

للأسف الرابط أعلاه مات منذ ذلك الحين.ومع ذلك، لا تزال هناك نسخة على Way Back Machine:

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

في نهاية المطاف، يعتمد الأمر على ما إذا كان تنسيق السلسلة الخاص بك سيتم استدعاؤه بشكل متكرر، على سبيل المثال.أنت تقوم ببعض المعالجة الجادة للنصوص التي يزيد حجمها عن 100 ميغابايت من النص، أو ما إذا كان يتم استدعاؤها عندما ينقر المستخدم على زر بين الحين والآخر.ما لم تكن تقوم ببعض مهام معالجة الدفعات الضخمة، سألتزم باستخدام String.Format، فهو يساعد في سهولة قراءة التعليمات البرمجية.إذا كنت تشك في وجود عنق الزجاجة في الأداء، فقم بلصق ملف التعريف على الكود الخاص بك وانظر أين هو بالفعل.

نصائح أخرى

من وثائق MSDN:

يعتمد أداء عملية التسلسل لكائن String أو StringBuilder على عدد مرات تخصيص الذاكرة.تقوم عملية تسلسل السلسلة دائمًا بتخصيص الذاكرة، بينما تقوم عملية تسلسل StringBuilder بتخصيص الذاكرة فقط إذا كان المخزن المؤقت لكائن StringBuilder صغيرًا جدًا بحيث لا يمكنه استيعاب البيانات الجديدة.وبالتالي، فإن فئة السلسلة هي الأفضل لعملية التسلسل إذا كان هناك عدد ثابت من كائنات السلسلة متسلسلة.في هذه الحالة، قد يتم دمج عمليات التسلسل الفردية في عملية واحدة بواسطة المترجم.يُفضل كائن StringBuilder لعملية التسلسل إذا كان هناك عدد عشوائي من السلاسل متسلسل؛على سبيل المثال، إذا قامت حلقة بتسلسل عدد عشوائي من سلاسل إدخال المستخدم.

لقد قمت بإجراء بعض مقاييس الأداء السريعة، وبالنسبة لـ 100000 عملية بمتوسط ​​أكثر من 10 عمليات تشغيل، فإن الطريقة الأولى (String Builder) تستغرق ما يقرب من نصف الوقت الذي تستغرقه الطريقة الثانية (String Format).

لذا، إذا كان هذا نادرًا، فلا يهم.ولكن إذا كانت عملية شائعة، فقد ترغب في استخدام الطريقة الأولى.

أتوقع String.Format أن تكون أبطأ - يجب أن تقوم بتحليل السلسلة و ثم قم بتسلسلها.

زوج من الملاحظات:

  • شكل هو الطريق الأمثل للسلاسل المرئية للمستخدم في التطبيقات الاحترافية؛وهذا يتجنب أخطاء الترجمة
  • إذا كنت تعرف طول السلسلة الناتجة مسبقًا، فاستخدم سترينج بيلدر (Int32) منشئ لتحديد القدرة مسبقا

فقط لأن string.Format لا يفعل بالضبط ما قد تعتقد، فإليك إعادة تشغيل الاختبارات بعد 6 سنوات على Net45.

لا يزال Concat هو الأسرع ولكنه في الحقيقة فرق أقل من 30%.يختلف StringBuilder والتنسيق بنسبة 5-10% بالكاد.حصلت على اختلافات بنسبة 20٪ في إجراء الاختبارات عدة مرات.

ميلي ثانية، مليون التكرار:

  • سلسلة:367
  • منشئ سلسلة جديد لكل مفتاح:452
  • منشئ السلسلة المخزنة مؤقتًا:419
  • سلسلة التنسيق: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);

أعتقد أنه في معظم الحالات، ينبغي أن يكون هذا الوضوح، وليس الكفاءة، هو أكبر اهتماماتك.ما لم تكن تقوم بجمع الكثير من الخيوط معًا، أو بناء شيء ما لجهاز محمول منخفض الطاقة، فمن المحتمل ألا يؤثر هذا كثيرًا على سرعة تشغيلك.

لقد وجدت أنه في الحالات التي أقوم فيها ببناء سلاسل بطريقة خطية إلى حد ما، فإن إجراء تسلسلات مستقيمة أو استخدام StringBuilder هو خيارك الأفضل.أقترح ذلك في الحالات التي تكون فيها غالبية السلسلة التي تقوم بإنشائها ديناميكية.نظرًا لأن القليل جدًا من النص ثابت، فإن الشيء الأكثر أهمية هو أنه من الواضح مكان وضع كل جزء من النص الديناميكي في حالة احتياجه إلى التحديث في المستقبل.

من ناحية أخرى، إذا كنت تتحدث عن جزء كبير من النص الثابت الذي يحتوي على متغيرين أو ثلاثة متغيرات، حتى لو كان أقل كفاءة، فأعتقد أن الوضوح الذي تكتسبه من string.Format يجعل الأمر يستحق كل هذا العناء.لقد استخدمت هذا في وقت سابق من هذا الأسبوع عندما اضطررت إلى وضع جزء واحد من النص الديناميكي في وسط مستند مكون من 4 صفحات.سيكون من الأسهل تحديث هذا الجزء الكبير من النص إذا كان في جزء واحد بدلاً من الاضطرار إلى تحديث ثلاث أجزاء تقوم بتسلسلها معًا.

يستخدم String.Format StringBuilder داخليًا... وهذا يؤدي بشكل منطقي إلى فكرة أنه سيكون أقل أداءً بسبب زيادة النفقات العامة.ومع ذلك، فإن تسلسل سلسلة بسيط هو أسرع طريقة لدمج سلسلة واحدة بين سلسلتين أخريين...بدرجة كبيرة.وقد أظهر ريكو مارياني هذا الدليل في أول اختبار أداء له منذ سنوات.الحقيقة البسيطة هي أن التسلسلات... عندما يكون عدد أجزاء السلسلة معروفًا (على سبيل المثال لا الحصر.. يمكنك وصل ألف جزء...طالما أنك تعرف أن عدد أجزاء السلسلة دائمًا 1000 جزء)... تكون دائمًا أسرع من StringBuilder أو String.Format.يمكن إجراؤها باستخدام تخصيص ذاكرة واحد وسلسلة من نسخ الذاكرة. هنا هو الدليل

وهذا هو الكود الفعلي لبعض أساليب String.Concat، والتي تستدعي في النهاية FileStringChecked الذي يستخدم المؤشرات لنسخ الذاكرة (المستخرجة عبر Reflector):

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!";

يتمتع!

أوه أيضًا، الأسرع سيكون:

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

هذا يعتمد حقا.بالنسبة للسلاسل الصغيرة التي تحتوي على عدد قليل من التسلسلات، يكون من الأسرع في الواقع إلحاق السلاسل فقط.

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

ولكن بالنسبة للسلاسل الأكبر حجمًا (السلاسل الكبيرة جدًا)، يكون استخدام StringBuilder أكثر كفاءة.

في كلتا الحالتين أعلاه أريد إدخال سلسلة واحدة أو أكثر في منتصف سلسلة قالب محددة مسبقًا.

في هذه الحالة، أود أن أقترح أن String.Format هو الأسرع لأنه مصمم لهذا الغرض بالضبط.

يعتمد الأمر حقًا على نمط الاستخدام الخاص بك.
معيار مفصل بين string.Join, string,Concat و string.Format يمكن العثور عليها هنا: String.Format غير مناسب للتسجيل المكثف

لا أقترح ذلك، نظرًا لأن String.Format لم يتم تصميمه للتسلسل، بل كان مصممًا لتنسيق مخرجات المدخلات المختلفة مثل التاريخ.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top