2017-09-28 18 views
2

私は巨大なデータセット(毎秒35の新しい値)で動作するUIプロジェクトに取り組んでいます。ユーザーはビューを10分から月ビューに変更することができます。これをアーカイブするために、私は自分自身にヘルパー関数を書いていました。この関数は大量のデータを600バイトの配列に切り捨てて、LiveView Chartに表示する必要があります。C#のパフォーマンスは急激に変化し、ちょうど0,001%増加します。

ソフトウェアは非常によく高速で動作することがわかりましたが、ソフトウェアの実行時間が長く(たとえば1か月間)、メモリ使用量が(約600Mbに)増加すると、の機能がかなり向上します遅い(最大8倍)。

私はこれの原因を見つけるためにいくつかのテストを行いました。かなり驚い私は39msランタイム

19msから7149571494ループを変更することで、私は本当に困惑している、機能取得の2倍遅くマジックナンバーのようなものがあることが分かりました。あなたが2番目のforループ(配列が切り捨てられているところ)をコメントアウトしても、それはずっと遅くなります。 これはGarbage Collectorと関係がありますか?または、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

Aha!私のシステムのしきい値は71924(〜20ms)と71925(〜30ms)の間です。 –

+0

それは違いを生むガベージコレクションのようです。ストップウォッチを開始する直前に 'GC.Collect();'を追加してみてください。私がそうすると、その違いは消えてしまいます。ガベージコレクションとあなた自身のコードを計時しているように見えます。 –

+2

ガベージコレクタであることをさらにサポートするには、 'fileValues'の初期化を' fileValues = new List (TEST); 'に変更してみてください。これによりリスト内の配列の再配置が妨げられ(最終リスト全体を保持するのに十分な大きさになるので)、収集するゴミはそれほど多くありません。私にとって、これは 'GC.Collect()'がなくても時間を短縮します。 –

答えて

2

あなたが示唆したように、この最も可能性が高い、ガベージコレクタによって引き起こされています。

私はこれがそうであることを示すために2つの証拠を提供することができます:

  1. あなたはストップウォッチを開始する直前にGC.Collect()を入れた場合は、時間の差は消えます。
  2. リストの初期化をnew List<ProductNameStream.FileValue>(TEST);に変更すると、時間の差もなくなります。

(項目ガベージコレクタへの圧力を減少させるであろう、それに追加されている間、そのコンストラクタで最終的なサイズにリストの容量を初期化は、その内部配列の複数の再配分を防止する。)したがって

、Iこの証拠に基づいて、あなたのタイミングに影響を与えているのが実際にガベージコレクタであると主張します。

ちなみに、しきい値は私と少しでも違っていました(タイミング差がガベージコレクタによって発生している場合は驚くことではありません)。

+0

リンクされたリストもうまくいきます(少し遅くなりますが、リストのようにガーベージは作成されません)。OPのレコードへのランダムアクセスは必要ありません。リンクリスト –

1

データ構造が非効率的で、計算中にたくさんの割り当てを行う必要があります。これを見てfixed size array inside a struct また、リストを事前に割り当てる。ゴミを生成するサイズを常に調整するためにリストに頼らないでください。

+0

私は私がC#でプログラミングを始めたときに安全でないコードを使用する。しかし、私はそれを試してみましょう。私はあなたの2番目のアイデアがもっと好きで、それについてまだ考えていませんでした。固定の600構造体配列にして、先頭にバイト配列を割り当てて、値を変更するだけです –

+0

必要なメモリがすべて事前に割り当てられている限り、固定バッファを使う必要はありません。これは、メモリを事前に割り当てる便利な方法です。固定バッファには、キャッシュ・パフォーマンスを向上させるという追加の利点があります。しかし、プロファイリングにどれだけ依存するでしょうか。リストを事前に割り当てておくと、きれいなスタートを確保できます。それだけでは役に立ちますが、究極の解決策ではありません。データサイズが大きくなると、問題が再び浮かび上がる可能性があります。 –

関連する問題