none
Arbeitsspeicherverwaltung RRS feed

  • Allgemeine Diskussion

  • (Win 7 64Bit; Intel Core2 Quad Q9300 mit 2,5GHz; 6GB RAM)

    Hallo C#-Gemeinde,

    ich schreibe grad ein Programm für die Uni und stehe vor dem Problem, dass ich aufgrund der Größe meines Modells öfter eine OutOfMemory-Exception bekomme. Beobachte dazu über den Ressourcenmonitor immer die Belegung des RAMs während der Laufzeit des Programms. Bei etwa 1,5GB zugewiesenen Speicher für mein Programm ist schluss.

    Jetzt war ich heute bei meiner Betreuerin und habe mein Programm bei ihr laufen lassen. Bei ihr wurde der Arbeitsspecher voll ausgenutzt. Sie hat insgesamt nur 4GB, weshalb bei etwa 3GB schluss war. Das reichte aber vollkommen aus, damit das Programm weiterlief.

    Abgesehen davon, dass ich ziemlich neidisch war, da mich dieses Problem schon den letzten Nerv gekostet hat, habe ich mich gefragt wieso sie den gesamten Arbeitsspeicher nutzen kann und ich nicht? Sie hat die gleiche Version des Betriebssystem und ebenfalls eine x64 Plattform. Der einzige Unterschied zwischen ihren und meinen Einstellungen war bei CPU: "Any CPU" - bei mir "x86". Als wir versucht haben das Programm mit x86 bei ihr zu starten bekam sie einen Fehler, dass der Provider (Microsoft.ACE.OLEDB.12.0) nicht auf dem Rechner registriert sei. Diese Anbindung an Office Access brauche ich zum einlesen von Daten aus einer Datenbank. Nachdem sie CPU auf "Any CPU" gesetzt hatte funktionierte es - wie beschrieben sogar besser als bei mir.

    Als ich versuchte die CPU Einstellung bei mir auf "Any CPU" zu ändern kam die gleiche Fehlermeldung, die sie auch erhalten hatte. Ich vermute, daher, dass sie eine Office 64bit Version hat und ich habe definitiv nur eine 32bit Version. Kann das sein?

    Dann meine zweite Frage: Wird x64 Programmen generell mehr Arbeitsspeicher zugewiesen oder warum konnte sie viel mehr Arbeitsspeicher nutzen als ich? Ich habe ihr System auch nochmal auf eine modifizierte Boot.ini geprüft. Alles standard. Jetzt stehe ich ein wenig auf dem Schlauch, da ich mehr RAM für meine Applikation brauche, das anscheinend auch geht aber nicht bei mir.

    Ich habe das Problem in meinem Program auch schon in Teilprobleme zerlegt und diese sequentiell gelöst, um den Arbeitsspeicher zu entlasten. Das half aber nichts, da der belegte Speicher nach Gebrauch anscheinend nicht schnell genug wieder freigegeben wird. Habs auch schon mit dem Grabber probiert. Hat hier vielleicht jemand noch Tipps?

    Freue mich auf jede hilfreiche Antwort, für die ich mich an dieser Stelle schon im Voraus bedanken möchte!

    Viele Grüße

    Tost1989

    Mittwoch, 1. Februar 2012 15:09

Alle Antworten

  • Hallo,

    ja, bei 32Bit gibt es eine Begrenzung von 2 GB (Davon stehen .Net jedoch nur ca. 1,5 GB zur Verfügung. Dies liegt an der Speicherverwaltung: Bei 32 Bit stehen dem ganzen System nur 4 GB speicher zur Verfügung und davon gehen dann gewisse Bereiche verloren (Adressbereich ist 4GB aber man will ja auch andere Geräte wie z.B. die Grafikkarte ansprechen können.). Und hier hat das 32it Subsystem (WOW64 - Windows on Windows 64Bit) die gleichen Limitationen wie ein 32 Bit System. Bei einem 32 Bit system kann man noch etwas tricksen um das Limit von 2 auf z.b. 3 GB anzuheben. Wie dies bei WOW64 geht, weiss ich jedoch nicht.

    Meine Empfehlung wäre, einfach einmal die 64Bit Version der Komponente herunter zu laden und dann die Software auch bei Dir als 64Bit Applikation laufen zu lassen. Download findest Du unter http://www.microsoft.com/downloads/de-de/details.aspx?FamilyID=c06b8369-60dd-4b64-a44b-84b371ede16d (Access Database Engine 2010 müsste sich doch hinter dem Microsoft.ACE.OLEDB.12.0 verbergen, oder?)

    Mit freundlichen Grüßen,

    Konrad


    Edit: Kleinen Hinweis hinzugefügt.
    Mittwoch, 1. Februar 2012 15:29
  • Hi,

    ich würde mir aber trotzdem Gedanken machen, warum du 1,5 GB RAM wegfrisst. Das ganze muss definitiv besser/schlanker gehen. Kannst du evtl. teile deines Codes posten, welche den Speicher voll machen? Evtl. können wir dir helfen, ohne dass du 64Bit Komponenten nutzen musst ;-)

    Grüße

    Mittwoch, 1. Februar 2012 15:54
  • Hallo,

    Dass deine Anwendung 1,5 GB RAM verbrauchen soll, ist schon wirklich bemerkenswert. Wenn Du da keine Raketen-Simulationen machst, vermute ich mal, dass Du das auch mit weniger Speicher durchziehen könntest. Die Frage, die sich für mich zunächst stellt ist also weniger, warum läuft's bei ihr und nicht bei mir (da könnte die Bitigkeit von Office durchaus eine Rolle spielen, aber auch andere Sachen wie Framework-Bitigkeit und Version), sondern warum in aller Welt verbraucht das Program so viel? Hast Du deine Anwendung schon mal mit einem Profiler analysiert, um herauszufinden, welches Allokations-Muster diesen Speicherverbrauch verursacht, bzw. was nicht freigegeben wird?

    Übrigens: Die Speicherbegrenzungen sind oft durch die Lizenzierungsmodelle des Betriebsystems bedingt. Während z.B. Windows Server 2008 Standard (32-bit) nur insges. 4GB unterstützt, ist die ebenfalls 32-bit Windows Server 2008 Datacenter-Version mit seinen 64GB wesentlich großzügiger. 

    M. Russinovich hat dazu einen sehr anschaulichen Artikel, den ich wärmstens empfehle:

    Pushing the Limits of Windows: Physical Memory
    http://blogs.technet.com/b/markrussinovich/archive/2008/07/21/3092070.aspx

    Memory Limits for Windows Releases
    http://msdn.microsoft.com/en-us/library/aa366778(VS.85).aspx

    Gruß
    Marcel

    Mittwoch, 1. Februar 2012 16:17
    Moderator
  • Hallo Konrad,

    das war eine sehr aufschlussrecihe Antwort die mein Bild von der Funktionsweise der Systeme klarer werden lässt. Die 64Bit Version der Komponente habe ich auch schon getestet, das hätte ich vielleicht noch erwähnen müssen. Allerdings muss ich hierzu das komplette Officepaket entfernen, dass ich in den nächsten Tagen jedoch dringend brauche. Ich werde den Versuch also auf die nächste verschieben müssen.

    Dann jedoch noch eine naive Frage: Reicht alleine die Installation dieser Komponente aus, damit ich die Daten auslesen kann? Müssen da nicht noch andere Officekomponenten, die im Office-Paket enthalten sind mit dabei sein? Zur Erinnerung: Ich habe lediglich Microsoft Office 2010 32Bit.

    Mir kommt aber grad noch ein anderer Gedanke: Könnte es eventuell auch ausreichen, wenn ich eine Office Access 2003 Version denistalliere, die ich aus Wartungsgründen für eine Datenbank noch auf meinem Rechner habe?

    Vielen Dank und mit den besten Grüßen

    Tost1989

    Mittwoch, 1. Februar 2012 16:32
  • Hallo Pawel,

    ich habe ein wenig gezögert den Code zu Posten, da es sich wie gesagt um eine Arbeit für die Uni handelt. Ich bitte daher nur um Ratschläge, Hinweise etc. damit man mir nacher nicht vorwerfen kann, dass ich in die Fußstapfen eines ehemaligen Verteidigungsministers trete.

    Ein weiterer Grund warum ich gezögert habe: Das Problem ist nicht mal eben so erklärt, da es doch etwas komplexer ist. Ich freue mich natürlich, dass du hier deine Hilfe anbietest, aber ich habe auch Verständnis dafür, wenn es dir zu viel Aufwand bereitet den Code zu verstehen.

    Zu meinem Problem: Es geht um Dienstplanung genauer gesagt um Dienstreihenfolgenplanung. Ich habe eine bstimmte Anzahl an Diensten gegeben, für die ich die optimale Konstellation finden soll. Dazu generiere ich mir Knoten, die jeweils eine zusammenhängende Folge von Diensten enthalten und lasse von einem Solver (CPLEX 12.0) die Knoten mit der besten Konstellation (niedrigste Kosten) heraussuchen.

    Dafür generiere ich zunächst für jeden Knoten alle möglichen und validen Variationen. Beispiel: Mein Knoten umfasst fünf Tage (ein Dienst pro Tag) und an jedem Tag kommen 15 Dienste in Frage. Macht also 5^15 Möglichkeiten, die Dienste miteinander zu kombinieren. Daher die enorme Komplexität des Problems. Habe teilweise Knoten mit 20Mio. Variationen. Damit das Problem nicht zu groß wird, teile ich mir nun die Betrachtung dieser Variationen auf - Teilmengen der gesamten Variationsmenge - und löse diese separat. Ich versuche es zumindest, aber wie oben beschrieben stoße ich an meine technischen Grenzen.

    Die folgende Funktion macht genau das, was ich gerade beschrieben habe. Sie ist noch nicht fertig und ich entschuldige mich schon einmal, wenn sie nicht gut lesbar ist. Ich habe versucht durch Kommentare das Vorgehen weitestgehend zu erläutern. Das erzeugen der Variationen kannst du dir wie das Durchprobieren aller Variationen eines Zahlenschlosses vorstellen. Du drehst das Rädchen (in diesem Fall die Dienstmenge für den entsprechenden Tag) immer eins weiter. Bist du einmal durch schiebst du das nächste Rädchen eins weiter und usw.. Die Zahlen in der Grafik unten stehen für jeweils einen bestimmten Dienst. Wie du sieht, müssen die jeweiligen Mengen nicht gleich Groß sein. Stell dir vor die erste Spalte Sei Montag, dann Dienstag usw.

    Die Übergabe an den Solver befindet sich am Ende. Das Problem hier ist, dass ich beim Solver ein Scalarprodukt aus der Entscheidungsvariable und den Gewichtungen meiner Zielfunktion bilden muss, was den Speicher zusätzlich enorm belastet, da das für jeden Knoten durchgeführt werden muss.

    public nodeObject[] varAllDuties2(nodeObject startNode)
            {
                /* Diese Methode gibt einen nodeObject-Array zurueck, das alle zu einem gegebenen Knoten
                 * gueltigen Variationen bezueglich der Dienste enthaelt.
                 */
    
                Duty[][] sheet = new Duty[startNode.NumOfDays][];
                int intNumOfVariations = 1;
                int[] intPointer = new int[startNode.NumOfDays];
    
                // Bestimme erlaubte Dienstmengen für jeden Tag i
                for (int i = 0; i < startNode.NumOfDays; i++)
                {
                    sheet[i] = getSetOfDutiesAll(startNode.ShiftIDs[i], (startNode.StartDay + i) % 7);
                }
    
                // Berechne Anzahl der Variationen der Dienste
                for (int i = 0; i < startNode.NumOfDays; i++)
                {
                    intNumOfVariations *= sheet[i].Length;
                }
                
                //MessageBox.Show("Anzahl der Variationen: " + intNumOfVariations);
    
                // Initialisiere Hilfsarray mit Zeigern, die die aktuelle Variation speichern
                for (int i = 0; i < startNode.NumOfDays; i++)
                {
                    intPointer[i] = 1;
                }
    
                // temporaeres Ergebnis-Array zweidimensional
                nodeObject[][] tempFinalResult = new nodeObject[splProb][];
    
                // Anzahl aller moeglichen Variationen
                int setSize = intNumOfVariations;
                int tempCounter = 0;
    
                // Fuer jedes Teilproblem...
                for (int t = 0; t < splProb; t++)
                {
                    // Zaehler fuer valide Dienst-Folgen
                    int intDiffValidVariations = 0;
    
                    // groeße der betrachteten Teilmenge
                    int splitSetSize = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(setSize) / Convert.ToDouble(splProb)));
                    
                    // tempraeres Array, in dem die Ergebnisse fuer das t'te Teilproblem gespeichert werden
                    nodeObject[] result = new nodeObject[splitSetSize];
                    
                    // Untergrenze der betrachteten Teilmenge t
                    int LB = t * splitSetSize;
    
                    // Obergrenze der betrachteten Teilmenge t
                    int UB = (t * splitSetSize) + splitSetSize;
    
                    // Für die Anzahl der Variationen des Teilproblems t
                    for (int i = LB; i < UB; i++)
                    {
                        bool boolTempValidSeq = true;
                        int intTime = 0;
                        double overTime = 0;
    
                        // Pruefe ob Schichtfolge zulaessig
                        for (int k = 0; k < startNode.NumOfDays - 1; k++)
                        {
                            if (isValidSequence(sheet[k][intPointer[k] - 1], sheet[k + 1][intPointer[k + 1] - 1]) == false)
                            {
                                boolTempValidSeq = false;
                                intDiffValidVariations++;
                                break;
                            }
                        }
    
                        // Wenn Schichtfolge zulaessig...
                        if (boolTempValidSeq == true)
                        {
                            Duty[] dutTempDutiesOne = new Duty[startNode.NumOfDays];
    
                            // Knoten generieren und in in result speichern
                            for (int k = 0; k < startNode.NumOfDays; k++)
                            {
                                dutTempDutiesOne[k] = sheet[k][intPointer[k] - 1];
                                intTime += sheet[k][intPointer[k] - 1].BDZ;
                                overTime += sheet[k][intPointer[k] - 1].BDZ - getGroupWorkingTimePerDay(startNode.GroupID);
                            }
                            overTime = overTime / startNode.NumOfDays;
                            result[i - intDiffValidVariations - (t * splitSetSize)] = new nodeObject(startNode.NodeID, startNode.GroupID, startNode.WeekNo, startNode.NumOfDays, startNode.StartDay, startNode.ShiftIDs, startNode.Cost, dutTempDutiesOne, intTime, overTime);
                        }
    
                        // Durchlaufe die oben ermittelten Dienstmengen (Zahlenschloss)
                        // Fuer Anzahl der Spalten (Tage)
                        for (int j = 0; j < startNode.NumOfDays; j++)
                        {
                            // Wenn Elementzeiger auf letztem Element von Spalte j dann...
                            if (intPointer[j] == sheet[j].Length)
                            {
                                // Zeiger zurücksetzen
                                intPointer[j] = 1;
                                // und zur naechsten Spalte gehen
                            }
    
                            // Ansonsten (Elementzeiger nicht auf letztem Element von Spalte j ...)
                            else
                            {
                                // Zeiger hochzählen und Schleife j abbrechen
                                intPointer[j] += 1;
                                break;
                            }
                        }
                    }
    
    
                    // Rueckgabearray zuschneiden
                    nodeObject[] tempResult = new nodeObject[result.Length - intDiffValidVariations];
    
                    for (int i = 0; i < result.Length - intDiffValidVariations; i++)
                    {
                        tempResult[i] = new nodeObject(result[i]);
                    }
                    
                    // Gebe Array result frei
                    result = null;
                    System.GC.Collect();
    
                    // Uebergabe an den Solver
                    tempFinalResult[t] = solveSubProbSplitNodeSet(tempResult);
                    
                    // Gebe Array tempResult frei
                    tempResult = null;
                    System.GC.Collect();
    
                    // Zaehle Ergebnisse
                    if (tempFinalResult[t] != null)
                    {
                        tempCounter += tempFinalResult[t].Length;
                    }
                }
                
                // Erzeuge Ergebnis-Array
                nodeObject[] finalResult = new nodeObject[tempCounter];
                int tCounter = 0;
                
                // Schreibe Ergebnisse in Ergebnisarray
                for (int i = 0; i < tempFinalResult.Length; i++)
                {
                    for (int j = 0; j < tempFinalResult[i].Length; j++)
                    {
                        finalResult[tCounter] = tempFinalResult[i][j];
                        tCounter++;
                    }
                }
                // MessageBox.Show("Ergebnisse: " + finalResult.Length);
                
                // Ergebnisse zurueckgeben
                return finalResult;
            }
    


    Der Vollständigkeit halber hier nochmal ein Ausschnitt aus der Funktion mit dem Solver zum Aufbau des Modells:

    // Entscheidungsvariabeln mit Unter- und Obergrenzen ----------------------------------------------
                        INumVar[] x = cplex.NumVarArray(numOfNodes, 0, 1);
    
                        // Zielfunktionen ---------------------------------------------------------------------------------
                        IObjective objSubProb = cplex.AddMinimize();
                        INumExpr maxAveragOverTime;
                        INumExpr[] tempMaxAveragOvertime = new INumExpr[numOfNodes];
                        INumExpr[] costNotChosenNode = new INumExpr[numOfNodes];
    
                        // Kosten fuer Abweichungen vom Wunschturnus und durchschnittliche Arbeitszeit der ausgewaehlten Knoten
                        for (int i = 0; i < numOfNodes; i++)
                        {
                            if (C1 > 0)
                            {
                                cplex.AddToExpr(objSubProb, cplex.Prod(C1, cplex.Prod(nodesDutyVar2D[t][i].Cost, x[i])));
                            }
                            if (C2 > 0)
                            {
                                cplex.AddToExpr(objSubProb, cplex.Prod(C2, cplex.Prod(nodesDutyVar2D[t][i].AverageWorkTime, x[i])));
                            }
                        }
    
                        // Kosten fuer nicht ausgewaehlte Knoten und maximale durchschnittliche Ueberstunden der ausgewaehlten Knoten
                        for (int i = 0; i < numOfNodes; i++)
                        {
                            costNotChosenNode[i] = cplex.Prod(-1, cplex.Prod(1000, cplex.Sum(-1, x[i])));
                            tempMaxAveragOvertime[i] = cplex.Prod(Convert.ToDouble(nodesDutyVar2D[t][i].AverageOverTime), x[i]);
                            cplex.AddToExpr(objSubProb, costNotChosenNode[i]);
                        }
                        maxAveragOverTime = cplex.Max(tempMaxAveragOvertime);
                        if (C3 > 0)
                        {
                            cplex.AddToExpr(objSubProb, cplex.Prod(C3, maxAveragOverTime));
                        }
    

    Vielen Dank für jede Hilfe!

    Viele Grüße

    Tost1989

    Mittwoch, 1. Februar 2012 17:10
  • Hallo Marcel,

    hast du mich also erwischt, wie ich Raketen-Simulationen für meinen guten alten Schulfreund Mahmud Ahmadinedschad durchführen wollte ;-). Nein, der Grund ist einfach die Komplexität meines Problems denke ich. Ich bin mir auch ziemlich sicher, dass es schlanker gehen muss. Aber bis jetzt wollte es noch nicht klappen.

    Ich bedanke mich natürlich auch bei dir für deine Ausführung zu den Plattformen und deren Speicherverwaltung.

    Grüße

    Tobias

    Mittwoch, 1. Februar 2012 17:37
  • Hallo Tobias,

    Na gut: Also doch Raketen ;-) 

    Ich glaube dir auch gern, dass dein Problem komplex ist. Aber verzeih mir bitte, wenn ich meinen positivistischen Zweifel nicht ganz vom Tisch fegen kann. Bevor ich auch nur daran denke, mir Code anzusehen, würde ich gerne erfahren, was genau sich auf den Heaps abspielt, wenn die OOM-Ausnahme geworfen wird. 

    Und bevor ich dich hier fernsteuere und nach den Ausgaben von .load sos, !dumpheap -stat usw. frage, verweise ich dich lieber auf einen älteren Artikel, der erklärt, wie man eine OOM-Ausnahme diagnostiziert (wahrscheinlich weißt Du das eh). Die Debugger-Befehle können im VS 2010 Direktfenster direkt eingegeben werden, manches stimmt auch nicht mehr (der Artikel ist aus dem Jahr 2006), aber die Grundprinzipien der Diagnose sind dort gut dargestellt.

    P.S. Ich hatte vor kurzem eine OOM-Exception in einem englischsprachigen Thread. Damals stellte sich heraus, dass tatsächlich die Komplexität bzw. die verwendeten und nicht freigegebenen Klassen (alle zu Generation 2 promoviert) die Ursache dafür waren. Der Code war aber nachvollziehbar und sehr viel einfacher als deiner. Könntest Du die OOM-Ausnahme in einem kurzen Codeschnippsel für uns reproduzierbar machen?

    Speicherproblemen auf den Grund gehen:
    http://msdn.microsoft.com/de-de/magazine/cc163528.aspx

    Gruß
    Marcel

    Mittwoch, 1. Februar 2012 18:34
    Moderator
  • Hallo Tobias,

    wenn ich dich richtig verstanden suchst du die optimale Konstellation, ich denk mal hier gibt es Vergleichskriterien (die du ja auch Anwenden wirst wenn, du alle Erzeugten Kombinationen Prüfst).


    Also wäre meine Idee, statt erst alle Kombinationen zu erstellen, immer nur die optimalere Konstellation behalten, b.z.w. du kannst auch mehrere behalten und die Kombinationen, die gar nicht zu gebrauchen sind verwerfen (Ich denk hier wird es dann einem Schwellwert geben).

    Beim über fliegen das Codes ist mir aufgefallen das du GC.Collect aufrufst.  Das kostet nur Leistung und wird nicht den gewünschten Effekt haben. Am besten ist wenn man den GC einfach seine Arbeit machen lässt.
    Mit freundlichen Grüßen

    Björn

     

     

    Freitag, 3. Februar 2012 09:51
  • ****************************************************************************************************************
    Dieser Thread wurde mangels weiterer Beteiligung des Fragestellenden ohne bestätigte Lösung abgeschlossen.
    Neue Rückfragen oder Ergänzungen zu diesem Thread bleiben weiterhin möglich.
    ****************************************************************************************************************

    Robert Breitenhofer, MICROSOFT  Twitter Facebook
    Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-Prinzip „Entwickler helfen Entwickler“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.

    Dienstag, 21. Februar 2012 09:15
    Moderator