2011-07-29 4 views
19

컬렉션 2 개 중첩 루프 안의 컬렉션에 새 개체를 추가하여 List < 컬렉션으로 작업하고 있습니다. 루프가 끝나면 컬렉션에 500,000 개가 추가됩니다.C# List <> Add() 메서드 성능

처음에는 adition 작업이 잘 실행되지만 성능 저하가 눈에 띄기 시작하자마자 마지막 수천 개의 요소에 대해 지연 시간은 견딜 수 없습니다.

List <> 컬렉션을 사용하여 다양한 트릭 (특정 크기 - 500000의 컬렉션 초기화)을 시도했지만 너무 도움이되지 않았습니다.

문제를 해결하기위한 팁을 알려주시겠습니까? 더 최적화 된 구조로 구조를 변경하는 것이 흥미 롭습니다 - LinkedList <> 예를 들어 추가 등의 작업을 통해 List <>보다 성능이 좋습니다. 상기 방법은 객체 (동시 사전)을 판독하고,로 (이 경우 LinkedList의 단위)리스트를 갱신 - :리스트에게있어서의

private void UpdateForecastList(ConcurrentDictionary<Int32, RegistroSalidaProductoPrevision> prediccion, bool soloMejoresMetodos = true) 
    { 
     foreach (KeyValuePair<int, RegistroSalidaProductoPrevision> kvp in prediccion) 
     { 
      KeyValuePair<int, RegistroSalidaProductoPrevision> localKvp = kvp; 

      IList<Prediccion> pExistente = prediccionList.Where(p => p.Id == localKvp.Key).ToList(); 

      Articulo articulo = (articuloList.Where(a => a.Id == localKvp.Key)).First(); 

      if (pExistente.Count > 0) 
      { 
       foreach (var p in pExistente) 
       { 
        prediccionList.Remove(p); 
       } 
      } 

      if (kvp.Value.Previsiones.Count > 0) 
      { 
       var previsiones = kvp.Value.Previsiones.Where(prevision => prevision.Value.LPrevision[1] != null).ToList(); 
       int previsionesCount = previsiones.Count; 

       for (int a = 0; a < previsionesCount; a++) 
       { 
        var registros = previsiones[a].Value.LPrevision[1].Serie; 
        int c = registros.Count; 

        if (soloMejoresMetodos) 
        { 
         if (localKvp.Value.MejorMetodo != previsiones[a].Key) continue; 
         for (int i = 0; i < c; i++) 
         { 
          var p = new Prediccion() 
             { 
              Id = articulo.Id, 
              Nombre = articulo.Codigo, 
              Descripcion = articulo.Descripcion, 
              NombreMetodo = 
               Utils.SplitStringByCapitals(previsiones[a].Value.NombreMetodo), 
              Fecha = registros[i].Fecha, 
              PrediccionArticulo = Math.Round(registros[i].Cantidad, 2), 
              EsMejorMetodo = 
               (previsiones[a].Value.NombreMetodo == localKvp.Value.MejorMetodo) 
                ? true 
                : false 
             }; 

          // This line experiences performance loss 
          prediccionList.Add(p); 
         } 
        } 
        else 
        { 
         for (int i = 0; i < c; i++) 
         { 
          prediccionList.Add(new Prediccion() 
                { 
                 Id = articulo.Id, 
                 Nombre = articulo.Codigo, 
                 Descripcion = articulo.Descripcion, 
                 NombreMetodo = previsiones[a].Value.NombreMetodo, 
                 Fecha = registros[i].Fecha, 
                 PrediccionArticulo = 
                  Math.Round(registros[i].Cantidad, 2), 
                 EsMejorMetodo = 
                  (previsiones[a].Value.NombreMetodo == 
                  localKvp.Value.MejorMetodo) 
                   ? true 
                   : false 
                }); 
         } 
        } 
       } 
      } 
      else 
      { 
       prediccionList.Add(new Prediccion() 
             { 
              Id = articulo.Id, 
              Nombre = articulo.Codigo, 
              Descripcion = articulo.Descripcion, 
              NombreMetodo = kvp.Value.ErroresDatos[0].Texto, 
             }); 
      } 
     } 
    } 

작은 설명을 업데이트

방법 특정 기사에 해당하는 예측.

동시 사전 객체는 동시에 액세스하는 다양한 스레드에서 지속적으로 업데이트됩니다.

목록은 모든 아티클에 해당하는 null 예측으로 초기화됩니다. 예를 들어 700 개의 기사가있는 경우 처음에는 목록에 700 개의 빈 예측이 채워집니다.

계산 쓰레드 중 하나가 concurent 사전을 업데이트하면 위에서 언급 한 방법을 호출하는 이벤트가 발생하고 차례로 목록 (prediccionList)이 업데이트됩니다.

prediccionList (이 경우)에서 보유 할 수있는 레코드의 최대 수는 약 500000 레코드이지만 목록에서 약 40000 개의 레코드를 추가 한 후에 성능 저하가 감지 될 수 있습니다.

다양한 최적화 기법 (예 : foreach'es 바꾸기, 루프 외부 계산, 목록 <> 개체를 LinkedList <> 등으로 대체)을 시도해 보니 코드가 약간 녹슬어 보일 수 있습니다. 마지막으로 실행 시간을 늦추는 부분은 "prediccionList.Add (p);"라는 결론에 도달했습니다.

목록에 추가 된 개체는 Prediccion 클래스의 인스턴스입니다. 이 개체는 내가 heacy가 아니라고 생각합니다. 단지 7 개의 필드만을 포함하고 있습니다.

메모리 사용량

결과를 메모리 프로파일 링에서 첨부합니다. 사용 된 메모리는 256MB를 능가하지 않아 메모리가 문제가 될 것이라고 생각하지 않습니다. enter image description here

+0

어디에서 500000 개 항목을 가져 오나요? –

+6

문제를 재현하는 코드 샘플을 제공 할 수 있습니까? – alun

+0

어떤 유형의 개체를 추가합니까? – jalf

답변

-2

더 빠른 (쿼리 할 수없는) 배열을 사용할 수 있습니다. 나는 당신의 코드의 특성을 모르지만 당신은 굴절시키고 데이터베이스를 사용할 수있다. 500000 항목은 결코 빠르지 않을 것입니다

1

List 대신 배열을 사용하는 것이 어떻습니까?초기 크기 (초기 500000 요소)로 초기화 할 수 있습니다. 충분하지 않은 경우 Array.Resize을 사용하여 100000을 더 추가하십시오. Length 속성은 실제 요소 수를 추적하기 만하면됩니다. 요소의 수.

그러나 기본적으로 새 크기의 새 배열이 생성되고 원래 배열의 모든 요소가 새 배열에 복사되므로 호출에 시간이 오래 걸릴 수 있습니다. 이것을 너무 자주 부르면 안됩니다.

+1

어떻게 도움이 될까요? 이미 그렇듯이 적절한 크기의 목록을 초기화하는 것과 어떻게 다른가요? – jalf

+1

@ jalf, 정확한 세부 정보는 모르겠지만 목록 아래에는 배열이 사용됩니다. 나는 새로운 배열을 생성하고 많은 요소가 관련되어있는 데이터를 복사하는 것이 꽤 많다고 생각합니다. 나는 완전히 잘못 생각할 수도 있습니다 :) –

+0

@ jalf, 나는'List <>'에 대한 구현이 "일반 배열"보다 더 많은 오버 헤드를 도입한다고 가정합니다. 나는'Array'를 사용하는 것이 더 빠르거나 나아 졌다는 것을 말하지 않고 있으며, 나는 단지 그것을 시도하고 어떤 일이 일어나는지를 제안 할뿐입니다. –

6

목록에 추가 할 개체의 크기가 큰 경우 메모리 제약이있을 수 있습니다.

프로세스가 32 비트 인 경우 주소 공간이 부족하기 전에 합계 2GB로 제한되지만, 64 비트 인 경우 시스템의 실제 메모리를 쉽게 초과하여 페이징을 시작할 수 있습니다 디스크에.

개체의 크기는 얼마나됩니까?

+0

어떻게 2GB를 계산했는지 모르겠지만 32 비트는 4GB입니다. – xxxxxxxxxadfas

+7

32 비트 프로세스에는 4GB 주소 공간의 2GB가 할당됩니다. 프로세스가 '대형 주소 인식'인 경우 32 비트 Windows에서 3GB, 64 비트 Windows에서 4GB를 액세스 할 수 있습니다. 64 비트 Windows에서 64 비트 프로세스는 8TB에 액세스 할 수 있습니다. –

+0

감사합니다! 크롤링 할 링크가 있습니까? – xxxxxxxxxadfas

6

내 경험에 의하면 List<T> 성능은 메모리에 따라 다릅니다. 항상 동일한 패턴을 따르고, 삽입은 빠르게 끝나고 성능이 급격히 떨어진다. 내 컴퓨터에서 1.2G 메모리 마크를 누르면 대개 발생합니다. 내가 시도한 거의 모든 컬렉션에서 동일한 문제가 발생 했으므로 List<T> 문제보다 .net 근본적인 문제가 더 많이 발생한다고 생각합니다.

나는 500,000을 사용하는 물건의 물건 크기를 줄이기 위해 (longs를 ints 등으로 대체하고) 시도해 보시기 바랍니다.
하지만 컴퓨터에서 빠르게 작동하도록 관리하더라도 앱이 배포 된 컴퓨터의 임계 값을 초과 할 수 있습니다.

5

목록이 더 큰 성장함에 따라, 그것은 때문에 가비지 수집기가 어떻게 작동하는 방식, 프레임 워크는 새로운 목록 위치로 내용을 복사하는, 쓰레기를 수집 확대 될 때마다. 그래서 더 커지면서 느리고 느려집니다. (GC on MSDN)

가능한 해결책 (내가 생각할 수있는)은 미리 정의 된 크기의 목록 또는 배열을 사용하고 있습니다. 채우기를하지 못하거나 옵션이 아닌 경우 System.Collections.Generic을 사용하십시오. LinkedList하지만 이미 시도 했으므로 해당되는 경우 단일 링크로 사용자 정의 목록을 구현해야 할 수도 있습니다 (LinkedList가 이중 링크 됨).

좋은 답변을 얻으려면 컬렉션에 보관하는 개체의 코드와 항목을 추가하는 부분을 게시해야합니다. 그러면 무엇이 더 좋은지 더 잘 이해할 수 있습니다.

또한 http://www.simple-talk.com/dotnet/performance/the-top-5-.net-memory-management-misconceptions/을 살펴보세요. 도움이 될 것으로 생각합니다.

UPDATE : 인덱싱 저렴 작업을해야하지만, 그럼에도 불구하고, 당신이 previsiones를 [A] (그리고 registros [i]를 중첩 루프) 루프의 시작에 지역 변수로, 당신은 indexings의 커플을 절약 할 수 읽으려고 할 수 있습니다 (clr이 이것을 최적화하지 않는다면 x 100000 반복, 약간의 차이가있을 수 있습니다.).

+0

> 확장 될 때마다 프레임 워크가 내용을 새 목록에 복사 중입니다. 이것은 제가 작성한 소설 중 가장 놀라운 부분입니다 원더 랜드에서 앨리스를 읽은 후 읽었습니다. 계속 고란을 써라. –

+1

@BoppityBop이 문제의 단어를 수정했습니다. 당신이 맞습니다. GC 작업이 어떻게 목록에 객체가 추가 될 때마다 오도 될 수 있는지 모르는 사람. –

0

초기화시 용량을 할당 해 보았습니까? 따라서 메모리를 다시 할당하고 오래된 내용을 새로운 메모리 공간으로 옮길 필요가 없습니다.

List<long> thelist = new List<long>(500000); 
1

클래스 대신 구조체를 사용하면 성능을 크게 향상시킬 수 있습니다.

또한 Prediccion Class/Struct에서 문자열 속성을 잃어 버리면 성능을 얻을 수 있습니다.

나는, 그래서 여기에 오랜 시간에 대한 실제 영향에 관심을 내 벤치 마크입니다 :

내가 다른 데이터 구조를 가져다가 그들에 20 만 개체/구조체를 넣어.

static void Main(string[] args) 
    { 
     const int noObjects = 20*1000*1000; 

     Console.WriteLine("List:"); 
     RunTest(new List<TestClass>(), noObjects); 
     RunTest(new List<TestStruct>(), noObjects); 
     Console.WriteLine(); 

     Console.WriteLine("Initialized List:"); 
     RunTest(new List<TestClass>(noObjects), noObjects); 
     RunTest(new List<TestStruct>(noObjects), noObjects); 
     Console.WriteLine(); 

     Console.WriteLine("LinkedList:"); 
     RunTest(new LinkedList<TestClass>(), noObjects); 
     RunTest(new LinkedList<TestStruct>(), noObjects); 
     Console.WriteLine(); 

     Console.WriteLine("HashSet:"); 
     RunTest(new HashSet<TestClass>(), noObjects); 
     RunTest(new HashSet<TestStruct>(), noObjects); 
     Console.WriteLine(); 

     Console.WriteLine("Now I added a string to the class/struct:"); 
     Console.WriteLine("List:"); 
     RunTest(new List<TestClassWithString>(), noObjects); 
     RunTest(new List<TestStructWithString>(), noObjects); 
     Console.WriteLine(); 

     Console.ReadLine(); 
    } 




    private static void RunTest<T>(ICollection<T> collection, int noObjects) where T : ITestThing 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Restart(); 
     for (int i = 0; i < noObjects; i++) 
     { 
      var obj = Activator.CreateInstance<T>(); 
      obj.Initialize(); 
      collection.Add(obj); 
     } 
     sw.Stop(); 
     Console.WriteLine("Adding " + noObjects + " " + typeof(T).Name + " to a " + collection.GetType().Name + " took " + sw.Elapsed.TotalMilliseconds + " ms"); 

     if (collection is IList) 
     { 
      IList list = (IList) collection; 
      // access all list objects 
      sw.Restart(); 
      for (int i = 0; i < noObjects; i++) 
      { 
       var obj = list[i]; 
      } 
      sw.Stop(); 
      Console.WriteLine("Accessing " + noObjects + " " + typeof (T).Name + " from a List took " + sw.Elapsed.TotalMilliseconds + " ms"); 
     } 
    } 

의 TestClass 및 TestStruct 모두는 다음과 같이 ('구조체'과 '클래스'하나, 하나) :

public class TestClass : ITestThing 
{ 
    public int I1; 
    public int I2; 
    public double D1; 
    public double D2; 
    public long L1; 
    public long L2; 

    public void Initialize() 
    { 
     D1 = 1; 
     D2 = 2; 
     I1 = 3; 
     I2 = 4; 
     L1 = 5; 
     L2 = 6; 
    } 
} 
이 내 테스트 프로그램

 
List: 
Adding 20000000 TestClass to a List`1 took 3563,2068 ms 
Accessing 20000000 TestClass from a List took 103,0203 ms 
Adding 20000000 TestStruct to a List`1 took 2239,9639 ms 
Accessing 20000000 TestStruct from a List took 254,3245 ms 

Initialized List: 
Adding 20000000 TestClass to a List`1 took 3774,772 ms 
Accessing 20000000 TestClass from a List took 99,0548 ms 
Adding 20000000 TestStruct to a List`1 took 1520,7765 ms 
Accessing 20000000 TestStruct from a List took 257,5064 ms 

LinkedList: 
Adding 20000000 TestClass to a LinkedList`1 took 6085,6478 ms 
Adding 20000000 TestStruct to a LinkedList`1 took 7771,2243 ms 

HashSet: 
Adding 20000000 TestClass to a HashSet`1 took 10816,8488 ms 
Adding 20000000 TestStruct to a HashSet`1 took 3694,5187 ms 

Now I added a string to the class/struct: 
List: 
Adding 20000000 TestClassWithString to a List`1 took 4925,1215 ms 
Accessing 20000000 TestClassWithString from a List took 120,0348 ms 
Adding 20000000 TestStructWithString to a List`1 took 3554,7463 ms 
Accessing 20000000 TestStructWithString from a List took 456,3299 ms

입니다 : 여기 결과입니다

public class 대신 TestStruct 만 public struct이고 "abc"로 초기화되는 TestClassWithString 및 TestStructWithString public string S1이 있습니다.

ITestThing은 struct가 Constructor를 가질 수 없기 때문에 거기에 있습니다. 그래서 일반적인 방법으로 Initialize() 메서드를 호출 할 방법이 필요했습니다. 그러나 Initialize()를 호출하면 많은 차이를 만들지 않습니다.) 또는 아닙니다.

인터페이스 또는 Activator.CreateInstance없이 모든 테스트 케이스에 대해 코드 일반을 작성했지만 두 번째 테스트를 추가하자마자 코드가 너무 커질 수 있습니다 당신은 크게 초기 크기의 목록을 사용하여 성능을 개선하고,하지 클래스 인스턴스 (객체)에 구조체를 넣을 수 있습니다

경우 ...

요약. 또한 모든 String 인스턴스가 다시 Object가 아닌 Struct를 사용하여 피하려고 시도한 객체이기 때문에 Structs에 String이 없도록하십시오.

4

이 문제는 List 또는 다른 .NET 데이터 구조의 성능과 관련이 없습니다. 문제는 순전히 알고리즘입니다.

foreach (KeyValuePair<int, RegistroSalidaProductoPrevision> kvp in prediccion) 
    { 
     KeyValuePair<int, RegistroSalidaProductoPrevision> localKvp = kvp; 

     IList<Prediccion> pExistente = prediccionList.Where(p => p.Id == localKvp.Key).ToList(); 

     Articulo articulo = (articuloList.Where(a => a.Id == localKvp.Key)).First(); 

그래서 사전 ( prediccion)의 모든 항목에 대해, 당신은 전체 prediccionList 반복하고 있습니다 : 예를 들어,이 코드 조각이있다. 당신은 n^2 알고리즘을 구현했습니다. 이 메서드를 실행하는 데 걸리는 시간은 prediccion.Count * prediccionList.Count에 비례합니다.

더 나은 알고리즘이 필요합니다. 빠른 수집 데이터 구조가 아닙니다.

+2

buhahaha .. 진짜 대답을 준 유일한 사람은 0 upvotes했고 그의 대답은 결국 .. 하나는 너무 사랑해야합니다 .. –

관련 문제