سؤال

في السؤال السابق حول تنسيق أ double[][] إلى تنسيق CSV، تم اقتراحه أن استخدام StringBuilder سيكون أسرع من String.Join.هل هذا صحيح؟

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

المحلول

اجابة قصيرة:هذا يعتمد.

اجابة طويلة: إذا كان لديك بالفعل مصفوفة من السلاسل لتسلسلها معًا (باستخدام محدد)، String.Join هي أسرع طريقة للقيام بذلك.

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

اذا أنت لا احصل على السلاسل كمصفوفة مسبقًا، إنها كذلك من المحتمل أسرع في الاستخدام StringBuilder - ولكن ستكون هناك حالات لا يكون فيها الأمر كذلك.إذا كان استخدام أ StringBuilder يعني عمل الكثير والكثير من النسخ، ثم بناء مصفوفة ثم الاتصال String.Join قد يكون أسرع.

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

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

نصائح أخرى

وهنا قال لي منصة الاختبار، وذلك باستخدام int[][] عن البساطة. النتائج أولا:

Join: 9420ms (chk: 210710000
OneBuilder: 9021ms (chk: 210710000

و(تحديث لنتائج double:)

Join: 11635ms (chk: 210710000
OneBuilder: 11385ms (chk: 210710000

و(تحديث إعادة 2048 * 64 * 150)

Join: 11620ms (chk: 206409600
OneBuilder: 11132ms (chk: 206409600

وومع OptimizeForTesting تمكين:

Join: 11180ms (chk: 206409600
OneBuilder: 10784ms (chk: 206409600

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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace ConsoleApplication2
{
    class Program
    {
        static void Collect()
        {
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
        }
        static void Main(string[] args)
        {
            const int ROWS = 500, COLS = 20, LOOPS = 2000;
            int[][] data = new int[ROWS][];
            Random rand = new Random(123456);
            for (int row = 0; row < ROWS; row++)
            {
                int[] cells = new int[COLS];
                for (int col = 0; col < COLS; col++)
                {
                    cells[col] = rand.Next();
                }
                data[row] = cells;
            }
            Collect();
            int chksum = 0;
            Stopwatch watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOPS; i++)
            {
                chksum += Join(data).Length;
            }
            watch.Stop();
            Console.WriteLine("Join: {0}ms (chk: {1}", watch.ElapsedMilliseconds, chksum);

            Collect();
            chksum = 0;
            watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOPS; i++)
            {
                chksum += OneBuilder(data).Length;
            }
            watch.Stop();
            Console.WriteLine("OneBuilder: {0}ms (chk: {1}", watch.ElapsedMilliseconds, chksum);

            Console.WriteLine("done");
            Console.ReadLine();
        }
        public static string Join(int[][] array)
        {
            return String.Join(Environment.NewLine,
                    Array.ConvertAll(array,
                      row => String.Join(",",
                        Array.ConvertAll(row, x => x.ToString()))));
        }
        public static string OneBuilder(IEnumerable<int[]> source)
        {
            StringBuilder sb = new StringBuilder();
            bool firstRow = true;
            foreach (var row in source)
            {
                if (firstRow)
                {
                    firstRow = false;
                }
                else
                {
                    sb.AppendLine();
                }
                if (row.Length > 0)
                {
                    sb.Append(row[0]);
                    for (int i = 1; i < row.Length; i++)
                    {
                        sb.Append(',').Append(row[i]);
                    }
                }
            }
            return sb.ToString();
        }
    }
}

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

ولقد خلق طريقتين اختبار لمقارنتها:

public static string TestStringJoin(double[][] array)
{
    return String.Join(Environment.NewLine,
        Array.ConvertAll(array,
            row => String.Join(",",
                       Array.ConvertAll(row, x => x.ToString()))));
}

public static string TestStringBuilder(double[][] source)
{
    // based on Marc Gravell's code

    StringBuilder sb = new StringBuilder();
    foreach (var row in source)
    {
        if (row.Length > 0)
        {
            sb.Append(row[0]);
            for (int i = 1; i < row.Length; i++)
            {
                sb.Append(',').Append(row[i]);
            }
        }
    }
    return sb.ToString();
}

وركضت كل طريقة من 50 مرة، ويمر في مجموعة من حجم [2048][64]. فعلت ذلك لمدة المصفوفات. شغل واحد مع الأصفار وأخرى مليئة القيم العشوائية. حصلت على النتائج التالية على الجهاز الخاص بي (P4 3.0 غيغاهرتز، أحادية النواة، لا HT، تشغيل وضع بيان من CMD):

// with zeros:
TestStringJoin    took 00:00:02.2755280
TestStringBuilder took 00:00:02.3536041

// with random values:
TestStringJoin    took 00:00:05.6412147
TestStringBuilder took 00:00:05.8394650

وزيادة حجم المصفوفة إلى [2048][512]، في حين خفض عدد التكرارات إلى 10 حصلت لي النتائج التالية:

// with zeros:
TestStringJoin    took 00:00:03.7146628
TestStringBuilder took 00:00:03.8886978

// with random values:
TestStringJoin    took 00:00:09.4991765
TestStringBuilder took 00:00:09.3033365

وكانت النتائج تكرارها (تقريبا؛ مع تقلبات صغيرة الناجمة عن القيم العشوائية مختلفة). يبدو String.Join هو أسرع قليلا في معظم الوقت (على الرغم من بهامش ضئيل جدا).

وهذا هو رمز استعملتها لاختبار:

const int Iterations = 50;
const int Rows = 2048;
const int Cols = 64; // 512

static void Main()
{
    OptimizeForTesting(); // set process priority to RealTime

    // test 1: zeros
    double[][] array = new double[Rows][];
    for (int i = 0; i < array.Length; ++i)
        array[i] = new double[Cols];

    CompareMethods(array);

    // test 2: random values
    Random random = new Random();
    double[] template = new double[Cols];
    for (int i = 0; i < template.Length; ++i)
        template[i] = random.NextDouble();

    for (int i = 0; i < array.Length; ++i)
        array[i] = template;

    CompareMethods(array);
}

static void CompareMethods(double[][] array)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < Iterations; ++i)
        TestStringJoin(array);
    stopwatch.Stop();
    Console.WriteLine("TestStringJoin    took " + stopwatch.Elapsed);

    stopwatch.Reset(); stopwatch.Start();
    for (int i = 0; i < Iterations; ++i)
        TestStringBuilder(array);
    stopwatch.Stop();
    Console.WriteLine("TestStringBuilder took " + stopwatch.Elapsed);

}

static void OptimizeForTesting()
{
    Thread.CurrentThread.Priority = ThreadPriority.Highest;
    Process currentProcess = Process.GetCurrentProcess();
    currentProcess.PriorityClass = ProcessPriorityClass.RealTime;
    if (Environment.ProcessorCount > 1) {
        // use last core only
        currentProcess.ProcessorAffinity
            = new IntPtr(1 << (Environment.ProcessorCount - 1));
    }
}

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

كان

وأتوود نوع آخر من ذات الصلة لهذا قبل نحو شهر:

http://www.codinghorror.com/blog/archives/001218.html

نعم.إذا قمت بإجراء أكثر من بضع صلات، فسيكون ذلك كثيراً أسرع.

عندما تقوم بإنشاء string.join، يجب أن يقوم وقت التشغيل بما يلي:

  1. تخصيص الذاكرة للسلسلة الناتجة
  2. انسخ محتويات السلسلة الأولى إلى بداية سلسلة الإخراج
  3. انسخ محتويات السلسلة الثانية إلى نهاية سلسلة الإخراج.

إذا قمت بصلتين، فيجب نسخ البيانات مرتين، وهكذا.

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top