[.net] 배열과리스트의 성능

자주 반복 해야하는 정수 목록 / 배열이 필요하다고 가정하면 매우 자주 의미합니다. 이유는 다양 할 수 있지만 대량 처리의 가장 중요한 내부 루프의 핵심이라고합니다.

일반적으로 크기가 유연하기 때문에 목록 (목록)을 사용하도록 선택합니다. 게다가 msdn documentation claims List는 내부적으로 배열을 사용하며 빠른 속도로 수행해야합니다 (Reflector를 사용하면 신속하게 확인할 수 있음). 그럼에도 불구하고 약간의 오버 헤드가 수반됩니다.

실제로 이것을 측정 한 사람이 있습니까? 목록을 통해 6M 번 반복하면 배열과 동일한 시간이 걸립니까?



답변

측정하기 매우 쉬운 …

길이가 고정되어 있음을 알고 있는 적은 수의 타이트 루프 처리 코드 에서이 작은 미세 최적화에 배열을 사용합니다. 인덱서를 / 양식에 사용 하면 배열이 약간 더 빠를 수 있지만 IIRC는 배열의 데이터 유형에 따라 다릅니다. 그러나 미세 최적화 가 필요 하지 않으면 단순하게 유지하고 등을 사용하십시오 .List<T>

물론 이것은 모든 데이터를 읽는 경우에만 적용됩니다. 키 기반 조회의 경우 사전이 더 빠릅니다.

다음은 “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();
    }
}


답변

요약:

  • 배열은 다음을 사용해야합니다.

    • 가능한 자주. 동일한 양의 정보를 얻기 위해 빠르며 RAM 범위가 가장 작습니다.
    • 필요한 정확한 세포 수를 알고 있다면
    • 배열 <85000b에 저장된 데이터 (정수 데이터의 경우 85000/32 = 2656 요소)
    • 높은 랜덤 액세스 속도가 필요한 경우
  • 목록을 사용해야합니다.

    • 목록 끝에 셀을 추가해야하는 경우 (종종)
    • 목록의 시작 / 중간에 셀을 추가해야하는 경우
    • 배열 <85000b에 저장된 데이터 (정수 데이터의 경우 85000/32 = 2656 요소)
    • 높은 랜덤 액세스 속도가 필요한 경우
  • LinkedList는 다음을 사용해야합니다.

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

    • 순차적 액세스 만 필요한 경우 (앞으로 / 뒤로)

    • 큰 항목을 저장해야하지만 항목 수가 적은 경우

    • 링크에 추가 메모리를 사용하므로 대량의 항목에는 사용하지 않는 것이 좋습니다.

      LinkedList가 필요한지 확실하지 않은 경우 IT가 필요하지 않습니다.


자세한 내용은:

색상 의미

배열 대 목록 대 연결 목록

훨씬 더 자세한 내용 :

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


답변

나는 성능이 상당히 비슷할 것이라고 생각합니다. 목록 대 배열을 사용할 때 발생하는 오버 헤드는 목록에 항목을 추가 할 때 및 배열의 ​​용량에 도달했을 때 목록에서 내부적으로 사용중인 배열의 크기를 늘려야 할 때의 IMHO입니다.

용량이 10 인 목록이 있다고 가정하면 11 번째 요소를 추가하려고하면 목록의 용량이 증가합니다. 목록의 용량을 보유 할 항목 수로 초기화하여 성능 영향을 줄일 수 있습니다.

그러나 List를 반복하는 것이 배열을 반복하는 것보다 빠른지 알아 내기 위해 벤치마킹하지 않는 이유는 무엇입니까?

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();

내 시스템에서; 배열을 반복하는 데 33msec가 걸렸습니다. 목록을 반복하는 데 66msec가 걸렸습니다.

솔직히 말해서, 나는 그 변화가 그렇게 될 것이라고 기대하지 않았습니다. 따라서 반복을 루프에 넣었습니다. 이제 반복을 1000 번 실행합니다. 결과는 다음과 같습니다.

목록을 반복하는 데 67146 msec가 소요됨 어레이를 반복하는 데 40821 msec가 소요됨

이제 변형은 더 이상 크지 않지만 여전히 …

따라서 .NET Reflector를 시작했으며 List 클래스 인덱서의 getter는 다음과 같습니다.

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

보시다시피 List의 인덱서를 사용할 때 List는 내부 배열의 경계를 벗어나지 않는지 확인합니다. 이 추가 점검에는 비용이 듭니다.


답변

루프가 아닌 하나의 값 중 하나에서 단일 값을 얻는 경우 두 가지 모두 경계 검사를 수행합니다 (관리 코드에 있음을 기억하십시오). 단지 목록이 두 번 수행합니다. 이것이 중요하지 않은 이유는 나중에 노트를 참조하십시오.

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

  • 정렬:
    • 경계 검사가 제거됩니다
  • 기울기
    • 경계 검사가 수행됩니다.

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

  • 정렬:
    • 반복을 관리하기 위해 할당 된 객체가 없습니다
    • 경계 검사가 제거됩니다
  • List로 알려진 변수를 통해 나열하십시오.
    • 반복 관리 변수는 스택 할당됩니다
    • 경계 검사가 수행됩니다.
  • IList로 알려진 변수를 통해 나열하십시오.
    • 반복 관리 변수는 힙 할당
    • 범위 검사도 수행됩니다. foreach 동안 값이 변경되지 않을 수 있지만 배열은 변경 될 수 있습니다.

경계 검사는 종종 큰 문제가되지 않습니다 (특히 요즘 깊은 파이프 라인 및 분기 예측이있는 CPU에있는 경우-대부분의 요즘 표준). 힙 할당을 피하는 코드 부분에있는 경우 (좋은 예제는 라이브러리 또는 해시 코드 구현) 변수를 IList가 아닌 List로 입력하면 함정을 피할 수 있습니다. 중요한 경우 항상 프로파일.


답변

[ 이 질문 도보십시오 ]

Marc의 답변을 수정하여 실제 난수를 사용하고 실제로 모든 경우에 동일한 작업을 수행합니다.

결과 :

         foreach를 위해
배열 : 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();
    }
}


답변

측정은 훌륭하지만 내부 루프에서 정확히 수행하는 작업에 따라 크게 다른 결과를 얻을 수 있습니다. 자신의 상황을 측정하십시오. 멀티 스레딩을 사용하는 경우 그 자체만으로는 사소한 작업이 아닙니다.


답변

실제로 루프 내에서 복잡한 계산을 수행하면 배열 인덱서 대 목록 인덱서의 성능이 약간 작아 질 수 있으므로 결국 중요하지 않습니다.