2017-06-21 3 views
0

Ich erstelle ein Berichtserstellungswerkzeug, das benutzerdefinierte Datentypen verschiedener Quellen aus unserem System verwendet. Der Benutzer kann ein Berichtsschema erstellen, und je nach Nachfrage werden die Daten basierend auf verschiedenen Indexschlüsseln, Zeitbereichen, Zeitbereichen usw. verknüpft. Das Projekt führt KEINE Abfragen in einer relationalen Datenbank durch, es ist reiner C# -Code in Sammlungen aus dem RAM.Sammlungsmanipulation, brauche Hilfe, diesen Code von einem Berichtsgenerator zu optimieren

Ich habe ein riesiges Leistungsproblem und ich sehe mir seit einigen Tagen meinen Code an und habe Mühe, ihn zu optimieren.

Ich reduzierte den Code auf das Minimum für ein kurzes Beispiel dessen, was der Profiler als den problematischen Algorithmus zeigt, aber die echte Version ist ein bisschen komplexer mit mehr Bedingungen und Arbeiten mit Daten.

Kurz gesagt, gibt diese Funktion eine Teilmenge von "Werten" zurück, die die Bedingungen in Abhängigkeit von den Schlüsseln der Werte erfüllen, die aus den "Indexreihen" ausgewählt wurden.

private List<LoadedDataSource> GetAssociatedValues(IReadOnlyCollection<List<LoadedDataSource>> indexRows, List<LoadedDataSource> values) 
{ 
    var checkContainers = ((ValueColumn.LinkKeys & ReportLinkKeys.ContainerId) > 0 && 
          values.Any(t => t.ContainerId.HasValue)); 

    var checkEnterpriseId = ((ValueColumn.LinkKeys & ReportLinkKeys.EnterpriseId) > 0 && 
          values.Any(t => t.EnterpriseId.HasValue)); 

    var ret = new List<LoadedDataSource>(); 
    foreach (var value in values) 
    { 
     var valid = true; 

     foreach (var index in indexRows) 
     { 
      // ContainerId 
      var indexConservedSource = index.AsEnumerable(); 
      if (checkContainers && index.CheckContainer && value.ContainerId.HasValue) 
      { 
       indexConservedSource = indexConservedSource.Where(t => t.ContainerId.HasValue && t.ContainerId.Value == value.ContainerId.Value); 
       if (!indexConservedSource.Any()) 
       { 
        valid = false; 
        break; 
       } 
      } 

      //EnterpriseId 
      if (checkEnterpriseId && index.CheckEnterpriseId && value.EnterpriseId.HasValue) 
      { 
       indexConservedSource = indexConservedSource.Where(t => t.EnterpriseId.HasValue && t.EnterpriseId.Value == value.EnterpriseId.Value); 
       if (!indexConservedSource.Any()) 
       { 
        valid = false; 
        break; 
       } 
      } 
     } 

     if (valid) 
      ret.Add(value); 
    } 

    return ret; 
} 

Dies funktioniert für kleine Proben, aber sobald ich Tausende von Werten und 2-3 Indexzeilen mit ein paar Dutzend Werte zu, kann es Stunden dauern, zu erzeugen.

Wie Sie sehen können, versuche ich zu brechen, sobald eine Indexbedingung fehlschlägt und zum nächsten Wert übergehe.

Ich könnte wahrscheinlich alles in einer einzigen "values.Where (####). ToList()", aber diese Bedingung wird schnell komplex.

Ich versuchte, ein IQueryable um IndexConservedSource zu erzeugen, aber es war noch schlechter. Ich versuchte, ein Parallel.ForEach mit einem ConcurrentBag für "ret" zu verwenden, und es war auch langsamer.

Was kann noch getan werden?

Antwort

1

Was Sie tun, ist im Prinzip, Schnittpunkt zweier Sequenzen zu berechnen. Sie verwenden zwei verschachtelte Schleifen und das ist langsam, da die Zeit O (m * n) ist. Sie haben zwei weitere Optionen:

  1. sortieren beide Sequenzen und kombiniere sie
  2. eine Sequenz in Hash-Tabelle konvertieren und testen Sie die zweite dagegen

Der zweite Ansatz für dieses Szenario scheint besser. Konvertieren Sie diese Indexlisten einfach in HashSet und testen Sie Werte dagegen. Ich habe einen Code für die Inspiration hinzugefügt:

private List<LoadedDataSource> GetAssociatedValues(IReadOnlyCollection<List<LoadedDataSource>> indexRows, List<LoadedDataSource> values) 
{ 
    var ret = values; 

    if ((ValueColumn.LinkKeys & ReportLinkKeys.ContainerId) > 0 && 
     ret.Any(t => t.ContainerId.HasValue)) 
    { 
     var indexes = indexRows 
      .Where(i => i.CheckContainer) 
      .Select(i => new HashSet<int>(i 
       .Where(h => h.ContainerId.HasValue) 
       .Select(h => h.ContainerId.Value))) 
      .ToList(); 

     ret = ret.Where(v => v.ContainerId == null 
         || indexes.All(i => i.Contains(v.ContainerId))) 
       .ToList(); 
    } 

    if ((ValueColumn.LinkKeys & ReportLinkKeys.EnterpriseId) > 0 && 
     ret.Any(t => t.EnterpriseId.HasValue)) 
    { 
     var indexes = indexRows 
      .Where(i => i.CheckEnterpriseId) 
      .Select(i => new HashSet<int>(i 
       .Where(h => h.EnterpriseId.HasValue) 
       .Select(h => h.EnterpriseId.Value))) 
      .ToList(); 

     ret = ret.Where(v => v.EnterpriseId == null 
         || indexes.All(i => i.Contains(v.EnterpriseId))) 
       .ToList(); 
    } 

    return ret; 
} 
+0

Vielen Dank für die Antwort.Leider, wenn ich Ihre Lösung nicht missverstehen, denke ich, es ist nicht so einfach. Ich habe keine zwei Sequenzen, ich habe eine Sequenz (Werte) und eine Sequenz von Sequenzen (Index). Damit ein Wert akzeptiert wird, muss er alle Bedingungen für mindestens ein Element in jedem Index bestehen. Ich sehe auch nicht, wie ich ein HashSet verwenden kann, da ich keinen einzigen Schlüssel zum Indizieren habe, sondern mehrere, die alle optional sind (ContainerId, EnterpriseId, DateStart, DateEnd, usw.). – Dunge

+0

Nachdem ich über HashSet ein bisschen mehr nachgedacht habe, verstehe ich was du meinst. Ich denke, ich könnte meine Daten anders organisieren. Anstatt eine Liste der "LoadedDataSource" -Klasse zu haben, die die verschiedenen Nullable-Schlüssel und den Wert enthält, könnte ich ein HashSet von jedem int-Schlüssel und eine sortierte Liste für Datumsschlüssel haben. Ich weiß nur nicht, wo ich den Wert setzen würde. Es wäre auch problematisch, die Reihenfolge beizubehalten, da meine Liste derzeit aufgrund einer anderen Bedingung vorsortiert ist. Ich müsste einen anderen Index behalten, um sie danach neu zu sortieren. Noch immer nicht vollkommen klar. – Dunge

+0

@Dunge Ich habe Code hinzugefügt, hoffe es hilft. –