Ответ 1

Короткий ответ: это зависит.

Длинный ответ: , если у вас уже есть массив строк для конкатенации вместе (с разделителем), String.Join - это самый быстрый способ сделать это.

String.Join может просматривать все строки, чтобы выработать нужную им длину, затем снова пойти и скопировать все данные. Это означает, что никакого дополнительного копирования не будет. Единственным недостатком является то, что он должен дважды проходить строки, что означает потенциальное продувание кеша памяти больше времени, чем необходимо.

Если у вас нет строк в массиве заранее, возможно, быстрее использовать StringBuilder - но будут ситуации, когда это не так. Если использование StringBuilder означает выполнение большого количества копий, то создание массива, а затем вызов String.Join может быть быстрее.

EDIT: это одноразовый вызов String.Join по сравнению с кучей вызовов StringBuilder.Append. В исходном вопросе у нас было два разных уровня вызовов String.Join, поэтому каждый из вложенных вызовов создал промежуточную строку. Другими словами, это еще сложнее и труднее догадываться. Я был бы удивлен, увидев в любом случае "выигрыш" значительно (с точки зрения сложности) с типичными данными.

EDIT: Когда я буду дома, я напишу тест, который так же болезнен, как возможно для StringBuilder. В принципе, если у вас есть массив, в котором каждый элемент примерно в два раза больше предыдущего, и вы получите его в самый раз, вы должны иметь возможность принудительно копировать для каждого добавления (элементов, а не разделителя, хотя это необходимо для также учитываются). В этот момент это почти так же плохо, как простая конкатенация строк, но String.Join не будет проблем.

Ответ 2

Здесь моя тестовая установка, используя int[][] для простоты; результаты:

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

(обновление для double результатов:)

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

(обновление re 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();
        }
    }
}

Ответ 3

Я так не думаю. При просмотре Reflector реализация 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 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));
    }
}

Ответ 4

Если разница 1% не превращается во что-то существенное с точки зрения времени выполнения всей программы, это выглядит как микро-оптимизация. Я бы написал код, который является наиболее читаемым/понятным и не беспокоится о разнице в производительности на 1%.

Ответ 6

да. Если вы сделаете больше, чем пару подключений, это будет намного быстрее.

Когда вы выполняете string.join, среда выполнения должна:

  • Выделить память для результирующей строки
  • скопировать содержимое первой строки в начало строки вывода
  • скопировать содержимое второй строки в конец выходной строки.

Если вы делаете два соединения, он должен скопировать данные дважды и т.д.

StringBuilder выделяет один буфер с запасным пространством, поэтому данные могут быть добавлены без копирования исходной строки. Поскольку в буфере остается пространство, добавленная строка может быть записана непосредственно в буфер. Затем просто нужно скопировать всю строку один раз, в конце.

Ответ 7

Конечно!

StringBuilder не является потокобезопасным, но рекомендуется для однопоточных программ, которые имеют манипуляции с строками.