2017-02-24 2 views
0

diesen Code Gegeben:OutOfMemory Ausnahme für Liste der POCOs

public class Customer 
{ 
    public int CustomerID { get; set; } 
    public string Name { get; set; } 
    public List<Qualification> Qualifications { get; set; } 
} 

public class Qualification 
{ 
    public QualificationType QualificationType { get; set; } 
    public decimal Value { get; set; } 
} 

public class Action 
{ 
    public ActionID { get; set; } 
    public int CustomerID { get; set; } 
    public decimal ActionValue { get; set; } 
} 

public class Service : IService 
{ 
    public List<Customer> ProcessCustomers() 
    { 
     List<Customer> customers = _customerService.GetCustomers(); // 250,000 Customers 
     List<Action> actions = _actionService.GetActions(); // 6,000 

     foreach (var action in actions) { 
      foreach (affectedCustomer in customers.Where(x => x.CustomerID < action.CustomerID)) { 
       affectedCustomer.Qualifications.Add(new Qualification { QualificationType = QualificationType.Normal, Value = action.ActionValue}); 
      } 

      foreach (affectedCustomer in customers.Where (x => SpecialRules(x))) { 
       affectedCustomer.Qualifications.Add(new Qualification { QualificationType = QualificationType.Special, Value = action.ActionValue}); 
      } 
     } 
    } 
} 

Der "Most Qualified" Kunde mit 12.000 Qualifikationen kann am Ende. Durchschnittlich können Kunden ~ 100 Qualifikationen erhalten.

Aber ich bekomme sehr früh ein OOME, nachdem ca. 50 Aktionen bearbeitet wurden. Zu diesem Zeitpunkt hat meine Liste noch immer nur 250.000 Kunden, aber es wurden etwa 5.000.000 Qualifikationen in die Kunden aufgenommen.

Ist das viel? Scheint mir ein wenig berauschend. Ich vermutete, dass ich Dutzende von Millionen von Kunden haben könnte, und jeder hat einen Durchschnitt von 1000 Qualifikationen und ist immer noch in Ordnung. Ich bin noch nicht einmal nah dran.

Was kann ich im Code tun, um das effizienter zu machen? Mir ist klar, dass ich die Ergebnisse jeder (oder mehrerer Gruppen) von Aktionen in eine Datenbank schreiben kann, aber ich möchte lieber so viel wie möglich im Speicher speichern, bevor ich die Ergebnisse schreibe.

Durchläuft die 6.000 Aktionen und fügt für jede Aktion Qualifikationen für eine variable Anzahl von Kunden hinzu. Für jede Aktion wird allen Kunden mit einer customerID> = dem aktionskausalen Kunden eine Qualifizierung hinzugefügt. Das sind also 1,2 Milliarden hinzugefügte Datensätze. Für jede Aktion erhalten 8-10 Kunden eine Qualifikation. Eine winzige 60.000 Datensätze im Vergleich zu den 1,2 Milliarden.

Ich habe versucht, dies im Speicher zu tun, weil ich Milliarden von Datensatzeinsätzen in eine DB nicht tun will. Ich benötige diese Datensatztrennung für den nächsten Verarbeitungsschritt, der die Kundenqualifikationen und die Unterschiede in den Schritten der CustomerIDs von oben nach unten betrachtet. Obwohl ich letztendlich Ergebnisse (komplexer als SUMs) in die Datenbank bringe. Aber ich kann diese Ergebnisse nur erreichen, wenn ich die Unterschiede in den einzelnen Qualifikationen, wie die Einstufung auf einer Kurve, betrachte.

+0

Ich würde gerne helfen, wenn Sie mir sagen, was Ihr Code tun soll :) –

+0

@EyalPerry Ich fügte hinzu, was das Ziel ist. – Suamere

Antwort

1

Die Anzahl der Objekte, die Sie herunterladen, ist wirklich riesig - Sie sollten in Betracht ziehen, die Daten in kleineren Blöcken zu verarbeiten, anstatt sie alle gleichzeitig herunterzuladen.

In .NET gibt es a limit of memory für einzelnes Objekt - Sie dürfen nie ein einzelnes Objekt erstellen, das 2 GiB überschreitet. Es has been lifted auf 64 Bit für .NET 4.5 für Arrays.

Eine Liste speichert Daten in einem Array. Wenn Sie alle Ihre Daten in eine Liste herunterladen, liegt die Größe des zugrunde liegenden Arrays über dem Grenzwert und Sie haben die OutOfMemory-Ausnahme.

+0

Ich verstehe nicht, warum eine 'Liste ' größer wird, wenn das 'Etwas' größer wird. Ich dachte, die Sammlung sei nur eine Sammlung von Zeigern auf Objekte. Anscheinend ist das in C# nicht so. Gibt es einen Weg, das zu erreichen? – Suamere

0

Ich habe die Bedeutung von SOLID-Code und ein explizites Domänenmodell für eine lange Zeit gepredigt. Ich bin nicht gezwungen worden, Domänenlogik zu schreiben, bei der man in ein paar Jahren Hunderttausende von Datenpunkten berücksichtigen muss. Dies ist, was ich in Bezug auf .NET-OOME gefunden habe:

  1. Eine Sammlung von Objekten ist keine Sammlung von Zeigern auf Objekte. Eine Sammlung ist selbst die Summe ihrer Teile.
  2. Für 32-Bit-Anwendungen kann eine App ~ 2GiB verwenden. Selbst wenn Sie große Sammlungen in kleinere Sätze von Sammlungen aufteilen, werden Sie nicht in der Lage sein, große Datenmengen zu betrachten.
  3. Objekte haben keine statischen Adressen. .Net ist frei, Objekte zu verschieben, es sei denn, Sie machen Ihren Code unsafe und zwingen die Objekte, klebrig zu sein. Aber selbst wenn Sie dies tun, unterliegen einzelne Objekte immer noch der maximalen Größe von ~ 2GiB (das ist in Ordnung), und die App unterliegt immer noch ~ 2 GB maximalem Speicher. Das Erstellen einer Sammlung von Zeigern ist daher keine Option.
  4. Web-Anwendungen (Web-API und ASP.Net) können nicht das IMAGE_FILE_LARGE_ADDRESS_AWARE-Flag verwenden, oder 64 große Anwendungen problemlos von dem, was ich sagen kann, würde ich gerne etwas anderes hören.

Die unglückliche Lösung

ich verpflichtet bin, meine Domain-Modell und haben einige Hacks zu brechen. Zum Beispiel: Statt einer Liste von Qualifikationen, die ich frei berechnen auf und die Summe kann, muss ich wie so eine Customer-Klasse haben:

public class Customer 
{ 
    public int CustomerID { get; set; } 
    public string Name { get; set; } 
    public decimal QualificationType1WithVariableType1Total { get; set; } 
    public decimal QualificationType1WithVariableType2Total { get; set; } 
    public decimal QualificationType2WithVariableType1Total { get; set; } 
    public decimal QualificationType2WithVariableType2Total { get; set; } 
} 

effektiv alle Berechnungen tun vorne und, wenn ich jemals andere vorstellen Variablen, ich muss eine "Total" Variable haben, mit der ich arbeiten kann. Das zu tun bedeutet; Anstatt einem Kunden Tausende Datensätze hinzuzufügen, hat dieser Kunde nur ein halbes Dutzend vorberechneter Felder mit einer Bedeutung, die ich später in Berechnungen verwenden kann.

So kann ich meinen Speicherbedarf verringern, aber ich bin nicht mehr in der Lage, meine Domäne explizit zu verwenden und Berechnungen frei auszuführen, während ich eine große Menge von Ergebnissen beobachte.

Zugegeben, diese Eigenschaften gab es technisch sowieso schon. Einige waren Readonly und führten eine spezielle LINQ-Gleichung basierend auf Zählungen, Durchschnitten und Summen durch. Einige waren Read/Write basierend auf dem Fortschritt anderer Kunden innerhalb von 100 CustomerIDs in einer linearen Kette. Stattdessen muss ich den gesamten Kontext wegwerfen und nur mit Summen arbeiten.

Ich bin nur verärgert darüber, dass ich heute mein kontextuelles Domänenmodell brechen muss, um in den Beschränkungen der Hardware zu arbeiten. Die Geschwindigkeit meiner App war sehr schnell und skalierte in der Nähe von O (1), Geschwindigkeit war also kein Problem.