배열과리스트의 성능


194

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

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

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


3
성능 문제를 제외하고는 고정 크기로 Lists보다 Arrays를 사용하는 것이 좋습니다 (물론 항목 수를 변경할 필요가없는 경우). 기존 코드를 읽을 때, 나는 그것이 도움이 빠르게 항목이 있는지 알고 찾을 강제 오히려 더 아래 기능의 코드를 검사하는 것보다, 크기를 고정 된 것으로.
워렌 스티븐스

2
T[]vs. List<T>는 큰 성능 차이를 만들 수 있습니다. 방금 .NET 4.0의 목록에서 배열로 이동하도록 매우 (중첩 된) 루프 집약적 응용 프로그램을 최적화했습니다. 5 ~ 10 % 향상을 기대했지만 속도가 40 % 이상 향상되었습니다! 목록에서 배열로 직접 이동하는 것 외에 다른 변경은 없습니다. 모든 열거는 foreach진술 로 수행되었습니다 . 마크 Gravell의 답변에 따라, 그것은 모양 foreach을 가진 것은 List<T>특히 좋지 않습니다.
특별 소스

답변:


221

측정하기 매우 쉬운 ...

길이가 고정되어 있음을 알고 있는 적은 수의 타이트 루프 처리 코드 에서이 작은 미세 최적화에 배열을 사용합니다. 인덱서를 / 양식에 사용 하면 배열이 약간 더 빠를 수 있지만 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();
    }
}

8
흥미로운 세부 사항 : 여기 내 장비의 RELEASE / DEBUG (.net 3.5 sp1) 시간이 있습니다 : 0.92, 0.80, 0.96, 0.93; 이는 VM에 Array / for 루프를 최적화하는 다른 지능보다 약 10 % 더 나은 지능이 있음을 알려줍니다.
David Schmitt

2
예, 배열 / for에 대한 JIT 최적화가 있습니다. 실제로, 길이 케이스 (고정 된 것을 알고 있기 때문에)가 포함되어 있다는 인상을 받았기 때문에 (내가 한 곳과 달리) 먼저 빼지 않은 이유는 무엇입니까? 업데이트 해 주셔서 감사합니다.
Marc Gravell

2
기묘한. 매우 유사한 테스트는 배열을 사용할 때 for와 foreach 사이에 큰 차이가 없음을 보여줍니다. 블로그 게시물에서 다른 사람들이 실행할 수있는 벤치 마크를 통해 철저히 조사 할 것입니다.
Jon Skeet

1
각 테스트 (chk1, chk2, chk3, chk4)에 대해 chk에 다른 변수를 사용하면 크게 다른 결과를 얻습니다. List / for = 1303ms, Array / for = 433ms입니다. 어떤 아이디어가 있습니까?
Jon

4
Jon이 Jon Skeet의 블로그에 대한 위의 주석에서 언급 한 링크가 끊어졌습니다. 아래는 업데이트 된 링크입니다. codeblog.jonskeet.uk/2009/01/29/…
Josh DeLong

88

요약:

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

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

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

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

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

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

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

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


자세한 내용은:

색상 의미

배열 대 목록 대 연결 목록

훨씬 더 자세한 내용 :

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


목록 접두사가 비교적 빠르지 만 삽입 속도가 느리다는 귀하의 주장에 약간 혼란 스럽습니다. 삽입은 또한 선형 시간이며, prepend보다 평균 50 % 빠릅니다.
Mike Marynowski

1
C # 목록의 @MikeMarynowski는 Array를 감싸는 래퍼입니다. 따라서 목록에 삽입하는 경우 특정 지점에 대해서만 선형 시간이 있습니다. 이 시스템은 새로운 하나의 더 큰 배열을 만들고 이전 항목에서 항목을 복사하는 데 시간이 필요합니다.
앤드류

접두사와 같은 것.
Mike Marynowski

프리 펜드 연산은 단지 0의 인서트입니다. 성능면에서 최악의 인서트이므로 인서트가 느리면 프리 펜 드가 더 느립니다.
Mike Marynowski

삽입과 접두사는 모두 O (n)입니다. 접두사는 삽입물이지만 목록의 모든 항목을 한 자리 위로 이동해야하기 때문에 가장 느린 삽입물입니다. 임의의 위치에있는 삽입물은 삽입 점보다 더 높은 인덱스에있는 항목 위로 올라가면 평균 50 %가됩니다.
Mike Marynowski

26

나는 성능이 상당히 비슷할 것이라고 생각합니다. 목록 대 배열을 사용할 때 발생하는 오버 헤드는 목록에 항목을 추가 할 때 및 배열의 ​​용량에 도달했을 때 목록에서 내부적으로 사용중인 배열의 크기를 늘려야 할 때의 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는 내부 배열의 경계를 벗어나지 않는지 확인합니다. 이 추가 점검에는 비용이 듭니다.


안녕하세요 Frederik, 감사합니다! 목록이 배열 시간의 두 배가 걸렸다 고 어떻게 설명하겠습니까? 당신이 기대하는 것이 아닙니다. 요소 수를 늘리려 고 했습니까?
보아스

1
이것을 반환하지 않습니다 ._items [index]; 인덱스가 범위를 벗어난 경우 이미 예외가 발생합니까? 최종 결과가 동일하거나없는 경우 .NET에서이 추가 검사를 수행하는 이유는 무엇입니까?
John Mercier

@John Mercier 검사는 목록의 크기 (현재 포함 된 항목 수)와 비교되며 _items 배열의 용량과 다르거 나 적습니다. 어레이는 용량이 초과하여 할당되어 추가 할 때마다 재할 당하지 않아도되므로 향후 항목을 더 빨리 추가 할 수 있습니다.
Trasvi

21

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

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

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

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

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

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


11

[ 이 질문 도보십시오 ]

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

이상합니다. / o + / debug- 명령 줄 (3.5SP1)에서 빌드 한 정확한 코드를 실행했으며 결과는 다음과 같습니다. list / for : 1524; 어레이 / for : 1472; 목록 / foreach : 4128; 배열 / foreach : 1484.
존 스키트

당신은 이것이 릴리스로 컴파일되었다고 말합니다-디버깅하지 않고 실행했는지 확인할 수 있습니까? 바보 같은 질문입니다. 알지만, 그렇지 않으면 결과를 설명 할 수 없습니다 ...
존 소총

2

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


2

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


2

다음은 IEnumerable 사전을 사용하는 것입니다.

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

2

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

공연

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

배열의 크기를 60k 배로 늘리면 속도가 느려집니다 ... 실제로 실제 사용량에서는 필요한 여분의 슬롯 수를 확인하고 길이 + 60k로 크기를 조정 한 다음 삽입물을 집어 넣는 것입니다.
tobriand

더 많은 공간이 필요할 때마다 크기를 두 배로 늘리면 배열의 크기를 빠르게 조정할 수 있습니다. 초기 선언 후 한 번만 크기를 조정하는 것과 동일한 시간이 소요되는 것으로 나타났습니다. 이는 목록의 유연성과 배열 속도의 대부분을 제공합니다.
user1318499

2

다른 답변에 게시 된 벤치 마크가 여전히 컴파일러가 루프를 최적화, 제거 또는 병합 할 수있는 여지가 남아 있기 때문에 걱정했습니다.

  • 예측할 수없는 입력 사용 (임의)
  • 결과를 콘솔에 인쇄하여 계산을 실행합니다.
  • 반복 할 때마다 입력 데이터를 수정합니다

직접 배열의 결과는 IList에 래핑 된 배열에 대한 액세스보다 약 250 % 더 나은 성능을 나타냅니다.

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

코드는 다음과 같습니다.

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;
}

0

List <>는 내부적으로 배열을 사용하므로 기본 성능은 동일해야합니다. 목록이 약간 느린 이유는 두 가지입니다.

  • 목록에서 요소를 조회하기 위해 기본 배열에서 조회하는 List 메소드가 호출됩니다. 따라서 추가 메소드 호출이 필요합니다. 반면에 컴파일러는이를 인식하고 "불필요한"호출을 최적화 할 수 있습니다.
  • 컴파일러는 배열의 크기를 알고 있으면 알 수없는 길이의 목록에 대해서는 수행 할 수없는 특별한 최적화를 수행 할 수 있습니다. 목록에 몇 가지 요소 만있는 경우 성능이 약간 향상 될 수 있습니다.

차이가 있는지 확인하려면 게시 된 타이밍 기능을 사용하려는 크기 목록으로 조정하고 특수 사례의 결과가 어떤지 확인하는 것이 가장 좋습니다.


0

비슷한 질문이 있었으므로 빠른 시작이되었습니다.

제 질문은 좀 더 구체적입니다. '반사 배열 구현을위한 가장 빠른 방법은 무엇입니까?'

Marc Gravell이 수행 한 테스트 결과는 많지만 액세스 타이밍이 정확하지는 않습니다. 그의 타이밍에는 배열과 목록에 대한 루핑이 포함됩니다. 또한 테스트하려는 세 번째 방법 인 '사전'을 비교하기 위해 hist 테스트 코드를 확장했습니다.

Firts, 상수를 사용하여 테스트를 수행하면 루프를 포함한 특정 타이밍을 얻을 수 있습니다. 실제 액세스를 제외한 '베 어린'타이밍입니다. 그런 다음 주제 구조에 액세스하여 테스트를 수행하면 '오버 헤드 포함'시간, 루프 및 실제 액세스가 제공됩니다.

'베어'타이밍과 '오버 헤드 통합'타이밍의 차이는 '구조 액세스'타이밍을 나타냅니다.

그러나이 타이밍은 얼마나 정확합니까? 테스트 기간 동안 창은 슈어를 위해 약간의 시간 슬라이싱을 수행합니다. 시간 슬라이싱에 대한 정보는 없지만 테스트 중에 수십 msec의 순서로 균등하게 분배된다고 가정합니다. 즉, 타이밍의 정확도는 +/- 100 msec 정도 여야합니다. 약간의 추정치? 어쨌든 체계적인 측정 오류의 원인.

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

따라서 두 개의 결과를 얻습니다. 하나는 상수이고 '(c)'로 표시된 것과 하나는 '(n)'으로 표시된 액세스에 대한 것입니다. 차이 'dt'는 실제 액세스에 걸리는 시간을 알려줍니다.

그리고 이것은 결과입니다 :

          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 / foreach의 차이점은 정체입니다. 어쩌면 현금이 필요할까요?

또한 배열에 액세스하기 위해 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);

0

몇 가지 간단한 테스트에서 두 가지의 조합이 합리적으로 집중적 인 수학이라고하는 것이 더 낫다는 것을 알았습니다.

유형: 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 언어가 될 수 있습니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.