2017-09-28 3 views
2

Je travaille sur un projet d'interface utilisateur qui doit fonctionner avec d'énormes jeux de données (toutes les 35 nouvelles valeurs) qui seront ensuite affichées dans un graphique. L'utilisateur doit être en mesure de changer la vue de 10 minutes à la vue mensuelle. Pour archiver cela, je me suis écrit une fonction d'aide qui tronque beaucoup de données à un tableau de 600 octets qui devrait ensuite être affiché sur un graphique LiveView. J'ai découvert qu'au début le logiciel fonctionne très bien et rapidement, mais plus le logiciel fonctionne (par exemple pendant un mois) et l'utilisation de la mémoire augmente (jusqu'à environ 600 mb), la fonction obtient beaucoup de plus lent (jusqu'à 8x).C# la performance change rapidement en bouclant seulement 0,001% plus souvent

J'ai donc fait quelques tests pour trouver la source de ceci. Très surpris, j'ai découvert qu'il ya quelque chose comme un chiffre magique où la fonction get 2x plus lent, en changeant 71494 boucles à 71495 de 19ms à 39MS exécution

Je suis vraiment confus. Même lorsque vous commentez la deuxième boucle for (où les tableaux sont tronqués), c'est beaucoup plus lent. Peut-être que cela a quelque chose à voir avec le Garbage Collector? Ou est-ce que C# compresse la mémoire automatiquement? Utilisation de Visual Studio 2017 avec les mises à jour les plus récentes.

Le code

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! Le seuil pour mon système est entre 71924 (prend ~ 20ms) et 71925 (prend ~ 30ms) –

+0

On dirait que c'est la collecte des ordures qui fait la différence. Essayez d'ajouter 'GC.Collect();' juste avant de lancer le chronomètre.Quand je fais cela, la différence disparaît - il semble donc que vous chronométrez le garbage collection ainsi que votre propre code. –

+2

Pour soutenir davantage mon affirmation selon laquelle il s'agit du garbage collector, essayez de changer l'initialisation de 'fileValues' en:' fileValues ​​= new List (TEST); '. Cela l'empêchera de réallouer le tableau à l'intérieur de la liste (puisqu'il sera assez grand pour contenir toute la liste finale) et donc il n'y aura pas autant de poubelles à collecter. Pour moi, cela réduit le temps sans le 'GC.Collect()'. –

Répondre

2

Ceci est le plus susceptible d'être causé par le collecteur d'ordures, comme vous le suggérez.

Je peux offrir deux éléments de preuve pour indiquer que tel est le cas:

  1. Si vous mettez GC.Collect() juste avant de commencer le chronomètre, la différence de temps va.
  2. Si vous modifiez plutôt l'initialisation de la liste à new List<ProductNameStream.FileValue>(TEST);, la différence dans le temps disparaît également.

(Initialiser la capacité de la liste à la taille finale dans son constructeur empêche plusieurs réaffectations de son tableau interne tandis que les éléments sont ajoutés à elle, ce qui réduira la pression sur le collecteur des ordures.)

Par conséquent, je Affirmer sur la base de cette preuve que c'est bien le ramasse-miettes qui a un impact sur vos horaires. Par ailleurs, la valeur de seuil était légèrement différente pour moi et pour au moins une autre personne (ce qui n'est pas surprenant si les différences de synchronisation sont causées par le garbage collector).

+0

liste liée fonctionne également bien ici (même si ce sera un peu plus lent mais il ne créera pas la liste des ordures), bien sûr si OP n'a pas besoin d'un accès aléatoire à ses enregistrements, il peut utiliser la liste chaînée –

1

Votre structure de données est inefficace et vous oblige à faire beaucoup d'allocations pendant le calcul. Jetez un oeil de ce fixed size array inside a struct . Préallouer également la liste. Ne comptez pas sur la liste pour ajuster constamment sa taille qui crée également des déchets.

+0

utiliser un code dangereux lorsque j'ai commencé à programmer en C#. Mais je vais essayer. J'aime beaucoup plus votre seconde idée, je n'y avais pas encore pensé. Je vais en faire un tableau struct fixe de 600 et allouer les tableaux d'octets au début, puis juste changer les valeurs –

+0

Tant que toute la mémoire nécessaire est préallouée, vous n'avez pas besoin d'utiliser un tampon fixe. C'est juste un moyen pratique de préallouer la mémoire. La mémoire tampon fixe présente l'avantage supplémentaire d'améliorer les performances du cache. Mais de combien dépendra le profilage. Préallouer la liste vous assure un bon départ. Cela seul aide, mais n'est pas la solution ultime. Le problème peut refaire surface lorsque la taille des données augmente. –