문제

안에 이전 질문 서식에 대해 a double[][] CSV 형식으로 제안되었습니다 그 사용 StringBuilder 보다 빠릅니다 String.Join. 이것이 사실입니까?

도움이 되었습니까?

해결책

짧은 대답 : 그것은 달라집니다.

긴 답변 : 이미 (구분자와 함께) 함께 연결하기 위해 많은 문자열이 있다면, String.Join 가장 빠른 방법입니다.

String.Join 모든 문자열을 살펴보면 필요한 정확한 길이를 해결 한 다음 다시 이동하여 모든 데이터를 복사 할 수 있습니다. 이것은있을 것임을 의미합니다 아니요 추가 복사. 그만큼 단점은 문자열을 두 번 통과해야한다는 것입니다. 즉, 메모리 캐시를 필요보다 더 많은 시간을 불어 넣을 수 있습니다.

만약 너라면 ~하지 않다 문자열을 미리 배열로 사용합니다. 아마 더 빨리 사용합니다 StringBuilder - 그러나 그렇지 않은 상황이있을 것입니다. A를 사용하는 경우 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

(RE 2048 * 64 * 150 업데이트)

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

그리고 최적화가 활성화 된 다음 :

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]. 나는 두 배열에 대해 이것을했다. 하나는 0으로 채워져 있고 다른 하나는 임의의 값으로 채워져 있습니다. 내 컴퓨터에서 다음과 같은 결과를 얻었습니다 (P4 3.0 GHz, 싱글 코어, 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% 성능 차이에 대해 걱정하지 않습니다.

Atwood는 약 한 달 전에 이것과 관련된 게시물을 가지고있었습니다.

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

네. 몇 가지 이상의 결합을하면 많이 더 빠르게.

string.join을 수행하면 런타임이 다음을 수행해야합니다.

  1. 결과 문자열에 메모리를 할당하십시오
  2. 첫 번째 문자열의 내용을 출력 문자열의 시작에 복사합니다.
  3. 두 번째 문자열의 내용을 출력 문자열 끝에 복사하십시오.

두 개의 결합을 수행하면 데이터를 두 번 복사해야합니다.

StringBuilder는 하나의 버퍼를 스페이스로 할당하므로 원래 문자열을 복사하지 않고도 데이터를 추가 할 수 있습니다. 버퍼에 남은 공간이 있으므로 추가 된 문자열은 버퍼에 직접 기록 될 수 있습니다. 그런 다음 끝에 전체 문자열을 한 번 복사하면됩니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top