programing

어레이의 퍼포먼스와리스트

topblog 2023. 4. 23. 09:59
반응형

어레이의 퍼포먼스와리스트

예를 들어 자주 반복해야 하는 정수의 목록/배열이 있어야 합니다. 즉, 매우 자주 반복해야 합니다.원인은 다양할 수 있지만, 대량 처리의 가장 내부적인 루프의 중심이라고 할 수 있습니다.

일반적으로 Lists(목록)는 크기가 유연하기 때문에 사용을 선택합니다.게다가 msdn 문서에서는, 리스트는 어레이를 내부적으로 사용하고 있기 때문에, 같은 속도로 동작할 수 있다고 하고 있습니다(리플렉터를 사용하면 간단하게 확인할 수 있습니다).물론, 약간의 오버헤드가 수반됩니다.

누가 이걸 실제로 쟀어요?목록을 통해 600만 번 반복하면 어레이와 같은 시간이 소요됩니까?

측정이 매우 용이합니다.

길이가 고정되어 있는 소수의 타이트 루프 처리 코드에서는 마이크로 최적화를 위해 어레이를 사용합니다.인덱서 / for form을 사용하면 어레이 속도약간 빨라지지만 IIRC는 어레이의 데이터 유형에 따라 다르다고 믿고 있습니다., 마이크로 최적화가 필요 없는 한 심플한 상태로List<T>syslog.

물론 모든 데이터를 읽는 경우에만 해당되며 키 기반 검색에는 사전이 더 빠릅니다.

다음은 "int"를 사용한 결과입니다(두 번째 숫자는 모두 동일한 작업을 수행했음을 확인하기 위한 체크섬입니다).

(버그 수정 필요)

List/for: 1971ms (589725196)
Array/for: 1864ms (589725196)
List/foreach: 3054ms (589725196)
Array/foreach: 1860ms (589725196)

테스트 리그를 기반으로 합니다.

using System;
using System.Collections.Generic;
using System.Diagnostics;
static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(12345);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next(5000));
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

간단한 답변:

.NET net경경List<T> ★★★★★★★★★★★★★★★★★」Array<T> 물 netList 싸여 있다Array.

한 번: 다시시시:List는 입니다.Array 으로! 안으로! 안으로.그물List<T>는 입니다.ArrayList<T>른른른른


다음의 경우에 사용할 필요가 있는 것을 상술합니다.

  • 어레이의 필요성:

    • 가능한 한 자주.고속으로 같은 양의 정보를 얻기 위해 최소 RAM 범위를 사용합니다.
    • 필요한 세포의 정확한 수를 알고 있다면
    • 어레이에 저장된 데이터가 85000 b 미만인 경우(정수 데이터의 경우 85000/32 = 2656 요소)
    • 고속 랜덤 액세스 속도가 필요한 경우
  • 사용할 목록:

    • 목록 끝에 셀을 추가해야 하는 경우(대부분)
    • 리스트의 선두/중간에 셀을 추가할 필요가 있는 경우(자주 추가하지 않음
    • 어레이에 저장된 데이터가 85000 b 미만인 경우(정수 데이터의 경우 85000/32 = 2656 요소)
    • 고속 랜덤 액세스 속도가 필요한 경우
  • Linked List는 다음을 사용해야 합니다.

    • 목록의 시작/중간/끝에 셀을 추가해야 하는 경우(대부분)

    • 시퀀셜 액세스만 필요한 경우(전진/후진)

    • 큰 아이템을 저장해야 하는데 아이템 수가 적은 경우.

    • 링크용 메모리를 추가로 사용하기 때문에 대량의 아이템에는 사용하지 않는 것이 좋습니다.

      Linked List가 필요한지 확실하지 않으면 필요하지 않습니다.

      그냥 사용하지 마세요.


상세:

색적 의미

어레이 vs 목록 vs 링크 목록

상세 정보:

https://stackoverflow.com/a/29263914/4423545

공연도 비슷할 것 같아요.목록 vs 어레이를 사용할 때 발생하는 오버헤드는 목록에 항목을 추가할 때 IMHO와 어레이 용량에 도달했을 때 내부적으로 사용할 어레이 크기를 늘릴 필요가 있는 경우입니다.

용량이 10인 목록이 있다고 가정하면, 11번째 요소를 추가하면 목록이 용량을 늘립니다.목록의 용량을 유지할 항목 수로 초기화하면 성능에 미치는 영향을 줄일 수 있습니다.

그러나 목록에 대한 반복이 어레이에 대한 반복만큼 빠른지 확인하려면 이 목록을 벤치마킹해 보십시오.

int numberOfElements = 6000000;

List<int> theList = new List<int> (numberOfElements);
int[] theArray = new int[numberOfElements];

for( int i = 0; i < numberOfElements; i++ )
{
    theList.Add (i);
    theArray[i] = i;
}

Stopwatch chrono = new Stopwatch ();

chrono.Start ();

int j;

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theList[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the List took {0} msec", chrono.ElapsedMilliseconds));

 chrono.Reset();

 chrono.Start();

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theArray[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the array took {0} msec", chrono.ElapsedMilliseconds));

 Console.ReadLine();

시스템에서는 어레이로 반복하는 데 33밀리초, 목록으로 반복하는 데 66밀리초가 걸렸습니다.

솔직히 그렇게까지 차이가 날 줄은 몰랐어요.반복을 루프에 넣었습니다.이제 두 반복을 모두 1000번 실행합니다.결과는 다음과 같습니다.

목록을 반복하는 데 67146밀리초가 소요되었습니다.어레이 반복에는 40821밀리초가 소요되었습니다.

지금은 그렇게 큰 편차는 아니지만 그래도...

그래서 제가 시작을 했습니다.NET Reflector 및 List 클래스의 인덱서 getter는 다음과 같습니다.

public T get_Item(int index)
{
    if (index >= this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    return this._items[index];
}

보시다시피 목록의 인덱서를 사용할 때 목록은 내부 배열의 범위를 벗어나지 않는지 확인합니다.이 추가 수표는 비용이 수반됩니다.

(루프가 아닌) 어느 한쪽에서 하나의 값만 얻을 경우, 두 가지 모두 경계 검사를 수행합니다(관리 코드 내에 있음). 목록만 두 번 수행합니다.이것이 왜 큰 문제가 되지 않는지에 대해서는 나중에 메모를 참조해 주세요.

자신의 것을 에 사용하고 있는 경우(int i = 0; i < x).[Length/Count];i++)의 주요 차이점은 다음과 같습니다.

  • ★★★★★★★★★★★★★★★★★★:
    • 경계 검사가 제거되었습니다.
  • 리스트
    • 경계 검사가 수행됩니다.

foreach를 사용하는 경우 주요 차이점은 다음과 같습니다.

  • ★★★★★★★★★★★★★★★★★★:
    • 반복을 관리하기 위해 개체가 할당되지 않았습니다.
    • 경계 검사가 제거되었습니다.
  • 목록으로 알려진 변수를 통해 목록을 표시합니다.
    • 반복 관리 변수가 스택에 할당됩니다.
    • 경계 검사가 수행됩니다.
  • IList 。
    • 반복 관리 변수가 힙 할당됨
    • bounds checking is performed foreach(경계 검사 수행) 리스트 값은 변경할 수 없지만 배열은 변경할 수 있습니다.

경계 체크는 큰 문제가 되지 않는 경우가 많지만(특히 파이프라인과 브랜치 예측이 깊은 CPU를 사용하고 있는 경우(대부분의 경우) 자신의 프로파일링만이 문제인지 아닌지를 알 수 있습니다.힙 할당을 회피하고 있는 코드의 일부(라이브러리 또는 해시 코드 실장에서의 좋은 예)의 경우 변수가 List not IList로 입력되면 해당 함정은 회피됩니다.중요한 일이라면 프로파일링도 항상 그렇겠지

[ 질문참고하세요]

실제 난수를 사용하여 모든 경우에 동일한 작업을 수행하도록 마크의 답변을 수정했습니다.

결과:

포어치용어레이: 1575ms 1575ms (+0%)목록: 1630ms 2627ms(+61%)(+3%)     (+67%)
(체크섬: -1000038876)

VS 2008 SP1에서 릴리즈로 컴파일.Q6600@2.40GHz, .NET 3.5 SP1에서 디버깅 없이 실행.

코드:

class Program
{
    static void Main(string[] args)
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(1);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next());
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = arr.Length;
            for (int i = 0; i < len; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
        Console.WriteLine();

        Console.ReadLine();
    }
}

다른 답변에 게재된 벤치마크가 컴파일러가 루프를 최적화, 제거 또는 병합할 수 있는 여지를 남길까 우려되어 다음과 같은 내용을 작성했습니다.

  • 예측 불가능한 입력 사용(랜덤)
  • 콘솔에 출력된 결과를 사용하여 계산 실행
  • 반복할 때마다 입력 데이터를 수정합니다.

그 결과, 다이렉트 어레이는 IList로 둘러싸인 어레이에 액세스 할 때보다 퍼포먼스가 약 250% 향상되었습니다.

  • 10억 어레이 액세스: 4000밀리초
  • 10억 개의 목록 액세스: 10000 ms
  • 1억 어레이 액세스: 350밀리초
  • 목록 액세스 수 1억 개: 1000 밀리초

코드는 다음과 같습니다.

static void Main(string[] args) {
  const int TestPointCount = 1000000;
  const int RepetitionCount = 1000;

  Stopwatch arrayTimer = new Stopwatch();
  Stopwatch listTimer = new Stopwatch();

  Point2[] points = new Point2[TestPointCount];
  var random = new Random();
  for (int index = 0; index < TestPointCount; ++index) {
    points[index].X = random.NextDouble();
    points[index].Y = random.NextDouble();
  }

  for (int repetition = 0; repetition <= RepetitionCount; ++repetition) {
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Start();
    }
    doWorkOnArray(points);
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Stop();
    }

    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Start();
    }
    doWorkOnList(points);
    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Stop();
    }
  }

  Console.WriteLine("Ignore this: " + points[0].X + points[0].Y);
  Console.WriteLine(
    string.Format(
      "{0} accesses on array took {1} ms",
      RepetitionCount * TestPointCount, arrayTimer.ElapsedMilliseconds
    )
  );
  Console.WriteLine(
    string.Format(
      "{0} accesses on list took {1} ms",
      RepetitionCount * TestPointCount, listTimer.ElapsedMilliseconds
    )
  );

}

private static void doWorkOnArray(Point2[] points) {
  var random = new Random();

  int pointCount = points.Length;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

private static void doWorkOnList(IList<Point2> points) {
  var random = new Random();

  int pointCount = points.Count;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

측정은 좋지만 내부 루프에서 정확히 무엇을 하고 있는지에 따라 상당히 다른 결과를 얻을 수 있습니다.당신의 상황을 측정하세요.멀티스레딩을 사용하는 경우 그것만으로는 간단한 작업이 아닙니다.

실제로 루프 내에서 복잡한 계산을 수행할 경우 어레이 인덱서와 목록 인덱서의 성능이 매우 미미하여 결국 문제가 되지 않을 수 있습니다.

IEnumberable 사전을 사용하는 것은 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);

        for (int i = 0; i < 6000000; i++)
        {
                list.Add(i);
        }
        Console.WriteLine("Count: {0}", list.Count);

        int[] arr = list.ToArray();
        IEnumerable<int> Ienumerable = list.ToArray();
        Dictionary<int, bool> dict = list.ToDictionary(x => x, y => true);

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }

        Console.WriteLine("Ienumerable/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }

        Console.WriteLine("Dict/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);


        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }

        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);



        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Ienumerable/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Dict/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

요소의 수를 늘려 용량을 추가하려고 하지 마십시오.

성능

List For Add: 1ms
Array For Add: 2397ms

    Stopwatch watch;
        #region --> List For Add <--

        List<int> intList = new List<int>();
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            intList.Add(rand.Next());
        }
        watch.Stop();
        Console.WriteLine("List For Add: {0}ms", watch.ElapsedMilliseconds);
        #endregion

        #region --> Array For Add <--

        int[] intArray = new int[0];
        watch = Stopwatch.StartNew();
        int sira = 0;
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            sira += 1;
            Array.Resize(ref intArray, intArray.Length + 1);
            intArray[rpt] = rand.Next();

        }
        watch.Stop();
        Console.WriteLine("Array For Add: {0}ms", watch.ElapsedMilliseconds);

        #endregion

몇 가지 간단한 테스트에서 이 두 가지 조합이 상당히 집약적인 수학에서 더 낫다는 것을 발견했습니다.

★★★★★List<double[]>

시간: 00:00:05.1861300

★★★★★List<List<double>>

시간: 00:00:05.7941351

★★★★★double[rows * columns]

시간: 00:00:06.0547118

코드 실행:

int rows = 10000;
int columns = 10000;

IMatrix Matrix = new IMatrix(rows, columns);

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();


for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] = Math.E;

for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] *= -Math.Log(Math.E);


stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;

Console.WriteLine(ts.ToString());

그런 최고급 하드웨어 액셀러레이션 매트릭스 클래스가 있었으면 좋겠어요.은 NET을 했습니다.System.Numerics.Vectors 반! 반!

C#은 이 분야에서 좀 더 많은 작업을 할 수 있는 최고의 ML 언어입니다.

List << 고객명 >> 님은 내부적으로 어레이를 사용하기 때문에 기본적인 퍼포먼스는 동일합니다.목록이 약간 느린 이유는 두 가지입니다.

  • 목록에서 요소를 검색하려면 List 메서드를 호출합니다. 이 메서드는 기본 배열에서 검색을 수행합니다.따라서 추가 메서드 호출이 필요합니다.한편, 컴파일러는 이것을 인식하고, 「불필요한」콜을 최적화할 수 있습니다.
  • 컴파일러는 어레이의 크기를 알고 있으면 길이를 알 수 없는 목록에서는 수행할 수 없는 특수한 최적화를 수행할 수 있습니다.목록에 요소가 몇 개만 있는 경우 성능이 향상될 수 있습니다.

어떤 차이가 있는지 확인하려면 게시된 타이밍 함수를 사용하려는 크기의 목록으로 조정하고 특별한 경우에 대한 결과를 확인하는 것이 좋습니다.

저도 비슷한 질문이 있어서 빨리 시작했어요.

좀 더 구체적인 질문은 '재귀적 어레이 구현에 가장 빠른 방법은 무엇인가?'입니다.

마크 그라벨이 실시한 테스트에서는 많은 것을 알 수 있지만, 액세스 타이밍은 정확하게 알 수 없습니다.타이밍에는 어레이 및 목록에서의 루프도 포함됩니다.테스트하고 싶은 세 번째 방법인 '사전'도 생각해냈기 때문에 히스토 테스트 코드를 확장했습니다.

첫째, 나는 루프를 포함한 일정한 타이밍을 주는 상수를 사용하여 테스트를 한다.이것은 실제 액세스를 제외한 '나쁜' 타이밍입니다.그런 다음 대상 구조에 액세스하여 테스트를 실시합니다.이것에 의해, 「오버헤드」타이밍, 루프, 및 실제의 액세스가 가능하게 됩니다.

'베어' 타이밍과 '오버헤드 포함' 타이밍의 차이는 '구조 접근' 타이밍을 나타냅니다.

하지만 이 타이밍은 얼마나 정확할까요?테스트 중에 창문은 슈어용으로 슬라이스할 수 있습니다.시간 슬라이싱에 대한 정보는 없지만 테스트 중에 수십 밀리초 정도 균등하게 분포되어 있기 때문에 타이밍의 정확도는 +/- 100 밀리초 정도여야 합니다.대충 어림잡아?어쨌든 체계적인 오류의 근원이지

또한 테스트는 최적화되지 않은 '디버깅' 모드에서 수행되었습니다.그렇지 않으면 컴파일러가 실제 테스트 코드를 변경할 수 있습니다.

따라서 상수, (c) 표시, (n) 표시 등 2개의 결과를 얻을 수 있으며, 실제 액세스에 걸리는 시간을 알 수 있습니다.

결과는 다음과 같습니다.

          Dictionary(c)/for: 1205ms (600000000)
          Dictionary(n)/for: 8046ms (589725196)
 dt = 6841

                List(c)/for: 1186ms (1189725196)
                List(n)/for: 2475ms (1779450392)
 dt = 1289

               Array(c)/for: 1019ms (600000000)
               Array(n)/for: 1266ms (589725196)
 dt = 247

 Dictionary[key](c)/foreach: 2738ms (600000000)
 Dictionary[key](n)/foreach: 10017ms (589725196)
 dt = 7279

            List(c)/foreach: 2480ms (600000000)
            List(n)/foreach: 2658ms (589725196)
 dt = 178

           Array(c)/foreach: 1300ms (600000000)
           Array(n)/foreach: 1592ms (589725196)
 dt = 292


 dt +/-.1 sec   for    foreach
 Dictionary     6.8       7.3
 List           1.3       0.2
 Array          0.2       0.3

 Same test, different system:
 dt +/- .1 sec  for    foreach
 Dictionary     14.4   12.0
       List      1.7    0.1
      Array      0.5    0.7

타이밍 오류(시간 슬라이싱으로 인한 체계적인 측정 오류를 제거하는 방법)에 대한 더 나은 추정치를 사용하면 결과에 대해 더 많은 것을 말할 수 있습니다.

List/Foreach가 가장 빠른 액세스 권한을 가진 것처럼 보이지만 오버헤드로 인해 중단되고 있습니다.

List/for와 List/forache의 차이는 stange입니다.혹시 현금화가 관련된 건 아닐까요?

에 대해서는, 「」, 「」, 「」를 .for 루우프foreach루프. 타이밍 결과와 그 정확성은 결과를 '비교'하게 만든다.

사전을 사용하는 것이 가장 느립니다. 왼쪽(인덱서)에는 이 테스트에서 사용되는 범위가 아닌 희박한 정수 목록이 있기 때문에 사전 사용만 고려했습니다.

수정된 테스트 코드입니다.

Dictionary<int, int> dict = new Dictionary<int, int>(6000000);
List<int> list = new List<int>(6000000);
Random rand = new Random(12345);
for (int i = 0; i < 6000000; i++)
{
    int n = rand.Next(5000);
    dict.Add(i, n);
    list.Add(n);
}
int[] arr = list.ToArray();

int chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // dict[i];
    }
}
watch.Stop();
long c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += dict[i];
    }
}
watch.Stop();
long n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // list[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(c)/for: {0}ms ({1})", c_dt, chk);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += list[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += 1; // arr[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("              Array(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += arr[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += 1; // dict[i]; ;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += dict[i]; ;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("          Array(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

@Marc Gravell의 답변에 덧붙일 두 가지 설명이 있습니다.

테스트는 에서 실시했습니다.NET 6(x64 릴리즈).

테스트 코드가 종료되었습니다.

어레이와 리스트가 같은 방법으로 테스트되지 않음

어레이와 List를 같은 조건으로 테스트하려면 "for"도 변경해야 합니다.

for (int i = 0; i < arr.Length; i++)

새 버전:

int len = arr.Length;
for (int i = 0; i < len; i++)

병목 목록/예측:

리스트(List/Foreach test)로 병목 현상을 수정할 수 있습니다.

다음과 같이 변경합니다.

list.ForEach(x => chk += x);

Windows 10 Pro 21H1 x 64 (Core i7-10510 탑재)노트북에서 테스트 실행u

List/for Count out: 1495ms (589725196)
List/for Count in: 1706ms (589725196)
Array/for Count out: 945ms (589725196)
Array/for Count in: 1072ms (589725196)
List/foreach: 2114ms (589725196)
List/foreach fixed: 1210ms (589725196)
Array/foreach: 1179ms (589725196)

결과 해석

Array/for(12%의 비율)

List/foreach fixedList/for.

List/foreach fixed is에 Array/foreach.

저는 이 테스트를 여러 번 실행했습니다.결과는 바뀌지만 규모 순서는 변경되지 않습니다.

이 테스트 결과를 보면 어레이를 강제로 사용하기 위해서는 퍼포먼스가 매우 필요함을 알 수 있습니다.

List를 조작하는 방법에 따라 퍼포먼스를 2로 나눌 수 있습니다.

이 테스트는 부분적입니다.랜덤 액세스, 직접 액세스, 쓰기 액세스 테스트 등은 없습니다.

제가 틀린 부분이 있나요? 아니면 성능 향상을 위한 다른 아이디어가 있나요?

테스트 코드:

using System;
using System.Collections.Generic;
using System.Diagnostics;
static class Program
{
    static void Main()
    {        List<int> list = new List<int>(6000000);
        Random rand = new Random(12345);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next(5000));
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for Count out: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < list.Count; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for Count in: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = arr.Length;
            for (int i = 0; i < len; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for Count out: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for Count in: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            list.ForEach(i => chk += i);
        }
        watch.Stop();
        Console.WriteLine("List/foreach fixed: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}
static long[] longs = new long[500000];
static long[] longs2 = {};
static List<long> listLongs = new List<long> { };
static void Main(string[] args)
{
    Console.CursorVisible = false;
    Stopwatch time = new Stopwatch();

    time.Start();
    for (int f = 50000000; f < 50255000; f++)
    {
        listLongs.Add(f);
    }

    //List  Time: 1ms    Count : 255000
    Console.WriteLine("List Time: " + time.ElapsedMilliseconds + " | Count: " + listLongs.Count());

    time.Restart();
    time.Start();
    for (long i = 1; i < 500000; i++)
    {
        longs[i] = i * 200;
    }

    //Array Time: 2ms Length: 500000 (Unrealistic Data)
    Console.WriteLine("Array Time: " + time.ElapsedMilliseconds + " | Length: " + longs.Length);

    time.Restart();
    time.Start();
    for (int i = 50000000; i < 50055000; i++)
    {
        longs2 = longs2.Append(i).ToArray();
    }

    //Array Time: 17950ms Length: 55000
    Console.WriteLine("Array Append Time: " + time.ElapsedMilliseconds + " | Length: " + longs2.Length);

    Console.ReadLine();
}
유형 시간을
어레이 2밀리초 500000
목록. 1밀리초 255000
어레이 추가 17950밀리초 55000

어레이에 소량의 데이터를 항상 추가할 계획이라면 목록이 더 빠릅니다.

이는 어레이를 어떻게 사용할 것인가에 달려 있습니다.

언급URL : https://stackoverflow.com/questions/454916/performance-of-arrays-vs-lists

반응형