2017-09-28 1 views
2

그래프로 표시되는 거대한 데이터 세트 (매 초마다 35 개의 새로운 값)를 사용해야하는 UI 프로젝트에서 작업하고 있습니다. 사용자는보기를 10 분에서 최대 월보기로 변경할 수 있습니다. 이것을 보관하기 위해 필자는 많은 데이터를 600 바이트 배열로 잘라내어 LiveView 차트에 표시해야하는 도우미 함수를 작성했습니다.C# 성능은 루핑을 통해 빠르게 0,001 %로 빠르게 변경됩니다.

처음에는 소프트웨어가 잘 작동하고 빠르지 만 소프트웨어가 오래 실행되고 (예를 들어 한 달 동안) 메모리 사용량이 올라 갔을 때 (약 600Mb까지) 함수가 많이 발생했습니다. 느림 (최대 8 배)

그래서 나는 이것에 대한 근원을 찾기 위해 몇 가지 테스트를했습니다. 아주 놀라 나는 매직 넘버 같은이 있음을 발견 어디에 기능 GET의 2 배, 느린 그냥 내가 정말 혼란 스러워요 39MS 런타임

19ms에서 71,49571494 루프를 변경하여. 두 번째 for 루프 (배열이 잘리는 부분)를 주석 처리한다고해도 훨씬 느립니다. 가비지 수집기와 관련이있을 수 있습니까? 아니면 C#이 자동으로 메모리를 압축합니까?

최신 업데이트로 Visual Studio 2017 사용. 당신이 제안

강령

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

namespace TempoaryTest 
{ 
    class ProductNameStream 
    { 
     public struct FileValue 
     { 

      public DateTime Time; 
      public ushort[] Value; 
      public ushort[] Avg1; 
      public ushort[] Avg2; 
      public ushort[] DAvg; 
      public ushort AlarmDelta; 
      public ushort AlarmAverage; 
      public ushort AlarmSum; 
     } 
    } 


    public static class Program 

    { 
     private const int MAX_MEASURE_MODEL = 600; 

     private const int TEST = 71494; 
     //private const int TEST = 71495;//this one doubles the consuming time! 
     public static void Main(string[] bleg) 
     { 
      List<ProductNameStream.FileValue> fileValues = new List<ProductNameStream.FileValue>(); 
      ProductNameStream.FileValue fil = new ProductNameStream.FileValue(); 

      DateTime testTime = DateTime.Now; 

      Console.WriteLine("TEST: {0} {1:X}", TEST, TEST); 
      //Creating example List 
      for (int n = 0; n < TEST; n++) 
      { 
       fil = new ProductNameStream.FileValue 
       { 
        Time = testTime = testTime.AddSeconds(1), 
        Value = new ushort[8], 
        Avg1 = new ushort[8], 
        Avg2 = new ushort[8], 
        DAvg = new ushort[8] 
       }; 
       for (int i = 0; i < 8; i++) 
       { 
        fil.Value[i] = (ushort)(n + i); 
        fil.Avg1[i] = (ushort)(TEST - n - i); 
        fil.Avg2[i] = (ushort)(n/(i + 1)); 
        fil.DAvg[i] = (ushort)(n * (i + 1)); 
       } 
       fil.AlarmDelta = (ushort)DateTime.Now.Ticks; 
       fil.AlarmAverage = (ushort)(fil.AlarmDelta/2); 
       fil.AlarmSum = (ushort)(n); 
       fileValues.Add(fil); 
      } 


      var sw = Stopwatch.StartNew(); 

      /* May look like the same as MAX_MEASURE_MODEL but since we use int 
      * as counter we must be aware of the int round down.*/ 
      int cnt = (fileValues.Count/(fileValues.Count/MAX_MEASURE_MODEL)) + 1; 

      ProductNameStream.FileValue[] newFileValues = new ProductNameStream.FileValue[cnt]; 
      ProductNameStream.FileValue[] fileValuesArray = fileValues.ToArray(); 


      //Truncate the big list to a 600 Array 
      for (int n = 0; n < fileValues.Count; n++) 
      { 
       if ((n % (fileValues.Count/MAX_MEASURE_MODEL)) == 0) 
       { 
        cnt = n/(fileValues.Count/MAX_MEASURE_MODEL); 
        newFileValues[cnt] = fileValuesArray[n]; 
        newFileValues[cnt].Value = new ushort[8]; 
        newFileValues[cnt].Avg1 = new ushort[8]; 
        newFileValues[cnt].Avg2 = new ushort[8]; 
        newFileValues[cnt].DAvg = new ushort[8]; 

       } 

       else 
       { 
        for (int i = 0; i < 8; i++) 
        { 
         if (newFileValues[cnt].Value[i] < fileValuesArray[n].Value[i]) 
          newFileValues[cnt].Value[i] = fileValuesArray[n].Value[i]; 
         if (newFileValues[cnt].Avg1[i] < fileValuesArray[n].Avg1[i]) 
          newFileValues[cnt].Avg1[i] = fileValuesArray[n].Avg1[i]; 
         if (newFileValues[cnt].Avg2[i] < fileValuesArray[n].Avg2[i]) 
          newFileValues[cnt].Avg2[i] = fileValuesArray[n].Avg2[i]; 
         if (newFileValues[cnt].DAvg[i] < fileValuesArray[n].DAvg[i]) 
          newFileValues[cnt].DAvg[i] = fileValuesArray[n].DAvg[i]; 
        } 
        if (newFileValues[cnt].AlarmSum < fileValuesArray[n].AlarmSum) 
         newFileValues[cnt].AlarmSum = fileValuesArray[n].AlarmSum; 
        if (newFileValues[cnt].AlarmDelta < fileValuesArray[n].AlarmDelta) 
         newFileValues[cnt].AlarmDelta = fileValuesArray[n].AlarmDelta; 
        if (newFileValues[cnt].AlarmAverage < fileValuesArray[n].AlarmAverage) 
         newFileValues[cnt].AlarmAverage = fileValuesArray[n].AlarmAverage; 
       } 
      } 
      Console.WriteLine(sw.ElapsedMilliseconds); 
     } 
    } 
} 
+0

아하! 내 시스템에 대한 임계 값은 71924 (~ 20ms 소요)와 71925 (~ 30ms 소요) 사이입니다. –

+0

차이를 만드는 가비지 콜렉션 인 것처럼 보입니다. 스톱워치를 시작하기 직전에'GC.Collect(); '를 추가하십시오.그렇게하면 차이점이 사라집니다. 가비지 컬렉션과 자신의 코드를 타이밍을 맞추는 것처럼 보입니다. –

+2

가비지 컬렉터라는 필자의 주장을 뒷받침하려면'fileValues'의 초기화를'fileValues ​​= new List (TEST);'로 변경해보십시오. 이렇게하면 List 내부에서 배열을 재 할당 할 수 없으므로 (최종 전체 목록을 담을만큼 충분히 커지기 때문에) 수집 할 쓰레기는 그리 많지 않을 것입니다. 나에게 이것은 GC.Collect()가 없어도 시간을 단축시킨다. –

답변

2

가능성이 가장 높습니다, 가비지 컬렉터에 의해 발생되고있다.

나는이 너무임을 나타내는 증거의 두 조각을 제공 할 수 있습니다 :

  1. 당신은 스톱워치를 시작하기 직전에 GC.Collect()를 넣어 경우, 시간의 차이는 사라집니다.
  2. 목록의 초기화를 new List<ProductNameStream.FileValue>(TEST);으로 변경하면 시간의 차이도 사라집니다.

(항목 가비지 컬렉터에 압력을 줄일 수있는 것이 첨가되는 동안 생성자의 최종 크기리스트의 용량이 초기화 방법은 어레이 내부의 다수의 재 할당을 방지한다.)

을 따라서 I 이 증거에 기반하여 실제로 타이밍에 영향을 미치는 가비지 컬렉터라고 주장하십시오.

덧붙여 말하자면, 임계 값은 저와 다른 사람도 약간 다릅니다 (타이밍 차이가 가비지 수집기로 인해 발생하는 경우 놀라운 일이 아닙니다).

+0

연결된 목록도 잘 작동합니다 (조금 느리지 만 목록과 같은 가비지를 만들지는 않겠지 만) OP에서 해당 레코드를 임의로 액세스 할 필요가없는 경우 링크 된 목록 –

1

데이터 구조가 비효율적이며 계산 중 많은 할당을해야합니다. 이것 좀 봐 fixed size array inside a struct . 또한 목록을 미리 할당하십시오. 목록에 의지하여 가비지를 생성하는 크기를 지속적으로 조정하지 마십시오.

+0

나는 결코 C#에서 프로그래밍을 시작할 때 안전하지 않은 코드를 사용하십시오. 그러나 나는 그것을 시험해 볼 것이다. 나는 두 번째 아이디어를 훨씬 더 좋아하고, 아직 그것에 대해 생각하지 않았다. 고정 600 구조체 배열을 만들고 처음에 바이트 배열을 할당 한 다음 값을 변경하십시오. –

+0

필요한 모든 메모리가 미리 할당되어있는 한 고정 버퍼를 사용할 필요가 없습니다. 이것은 메모리를 미리 할당하는 편리한 방법입니다. 고정 버퍼에는 캐시 성능을 향상시키는 추가 이점이 있습니다. 그러나 프로파일 링에 얼마나 의존 할 것인가. 목록을 사전 할당하면 처음부터 깨끗하게 시작할 수 있습니다. 그것만으로도 도움이되지만 궁극적 인 해결책은 아닙니다. 데이터 크기가 커지면 문제가 다시 발생할 수 있습니다. –