C# Beschränkungen im DEBUG-mode

Berba

Lt. Junior Grade
Registriert
Okt. 2007
Beiträge
294
um es kurz zu machen

ich habe eine eigene klasse mit 4 eigenschaften erstellt. im folgenden werden innerhalb einer "dreifachen schleife" 513.500.000 objekte der klasse erstellt mit jeweils unterschiedlichen eigenschaften.

sämtliche objekte werden mittels sequentiellem und parallelem linq abgefragt.

ich arbeite im debug-mode. kompiliert werden soll auf x86

das problem: während des ablaufs bleibt im debug-mode nach 10 sekunden das programm hängen mit der fehlermeldung "outofmemory-exception"..dies während der linq-abfrage

im release-mode passiert auch nach 5 minuten nichts und das programm ist noch am rechnen

liegen irgendwelche restriktionen vor? kann in 32bit nicht kompiliert werden bei so vielen objekten? reichen etwa 4gb ram nicht aus?

wo liegt der fehler?? =))
vielen dank an jeden helfer
 
Bei 513.500.000 Objekten würde allein ein 32bit Pointer fürs Objekt selber ja bereits knapp 2GB RAM fressen. Und da wird ja noch mehr dran sein an den Objekten. Debug benötigt wohl noch etwas dazu pro Instanz. In dem Sinne: Zu wenig Speicher. Ich frag mich aber wie das überhaupt klappt unter x86 - dort hast du eigentlich auch nur 2GB pro Prozess zur Verfügung.
 
Ich frag mich auch, wie das klappt. Ich kann es mir nur so vorstellen, dass .Net ja ähnlich wie eine Java VM funktioniert und daher die üblichen Beschränkungen so nicht ganz gelten.


Bei dieser Menge an Daten:
Kannst du das nicht in einem oder mehreren Arrays organisieren? Müssen es zwangsweise Objekte sein?
 
hm ich glaube es liegt daran, dass doch im release-mode gar keine exceptions geworfen werden oder?!? -.-

ps. bei 10,3 millionen objekten ists ne sache von 5 sekunden die auszuwerten
Ergänzung ()

kann euch das, schlecht programmierte beispiel natürlich, gerne zur verfügung stellen..sind ja nur wenige zeilen code
 
Sicher, dass im Release-Mode keine Exceptions geworfen werden, die du nur nicht siehst?
Vermutlich läuft es nur mit den Objekten weiter, die erstellt worden sind und nicht mit allen, die du erzeugen möchtest. Wer braucht auch schon 513.500.000 Objekte?

Selbst wenn nur ein Objekt 10 Bytes groß ist, verbrauchst du mit deinen Objekten schon über 4 GB Speicher. Ein 32 Bit System kann gar nicht soviel verwalten. Es kann maximal 3 GB verwalten.
 
1.) Das Beispiel wäre von Vorteil. Da lässt sich sicher etwas optimieren. Im Normalfall sollte pro Array nie mehr als 10MB RAM verbraten. Das ist bei mir immer die Grenze, an der ich überlege, ob es nicht besser geht.

2.) Im Release Modus werden sehr wohl Exceptions geworfen und du kannst auch ganz normal debuggen (zumindest unter VB.NET, nehme an, dass es mit C# genauso läuft). Du bekommst sogar Zeilennummern bei den Exceptions, wenn du die .pdb Files mit auslieferst. Das Einzige was nicht geht, was mir bisher aufgefallen ist, ist unter x86 den Code während der Laufzeit zu verändern und nachträglich ein paar Anweisungen einzubauen, aber das sollte man sowieso nicht machen. Ich teste immer ausschließlich im Release Modus.

3.) LINQ und Performance schließt sich eigentlich so gut wie immer aus. Ich halte von LINQ nicht allzu viel. Es ist zwar eine nette Idee, nur meistens kommt katastrophaler Code dabei raus.
 
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApplication2
{
    public class Person
    {
        public string Name { get; set; }
        public string Städtchen { get; set; }
        public int Alter { get; set; }
        public string Nachname {get;set;}
    }

    class Program
    {
        static void Main(string[] args)
        {
            ArrayList Namenliste = new ArrayList();                                 //Initialisieren der Arraylisten die mit Daten aus den Dateien gefüllt werden
            ArrayList Alterliste = new ArrayList();
            ArrayList Stadtliste = new ArrayList();
            ArrayList Personenliste = new ArrayList();
            ArrayList Namenliste2 = new ArrayList();
            Stopwatch Abfrageuhrpar = new Stopwatch();                                 // Initialisieren der Stoppuhren
            Stopwatch Abfrageuhrseq = new Stopwatch();                                 //

            Random Alter = new Random();                                               // Initialisieren des Objektes Alter aus der Klasse Random(Erzeugung von Zufallszahlen)
            int i = 0;
            int k = 0;
            int l = 0;
            StreamReader frauennamen = new StreamReader("frauennamen.txt");
            string eingelesenezeile = "";

            while (eingelesenezeile != null)                                            //  Einlesen der Datei "frauennamen.txt" und Schreiben der Daten in
            {                                                                           //  die Arraylist "Namenliste"
                eingelesenezeile = Convert.ToString(frauennamen.ReadLine());            //
                Namenliste.Add(eingelesenezeile); i++;                                  //
            }                                                                           //
            Namenliste.RemoveAt(i - 1);                                                 //

            eingelesenezeile = "";

            StreamReader Städte = new StreamReader("Städte.txt");                   //


            while (eingelesenezeile != null)                                            //  Einlesen der Datei "Städte.txt" und Schreiben der Daten in
            {                                                                           //  die Arraylist "Stadtliste"
                eingelesenezeile = Convert.ToString(Städte.ReadLine());                 //
                Stadtliste.Add(eingelesenezeile); k++;                                  //
            }                                                                           //
            Stadtliste.RemoveAt(k - 1);

             eingelesenezeile = "";

            StreamReader Menner = new StreamReader("Menner.txt");                   //


            while (eingelesenezeile != null && l <= 10)                                            //  Einlesen der Datei "Städte.txt" und Schreiben der Daten in
            {                                                                           //  die Arraylist "Stadtliste"
                eingelesenezeile = Convert.ToString(Menner.ReadLine());                 //
                Namenliste2.Add(eingelesenezeile); l++;                                  //
            }                                                                           //
            Namenliste2.RemoveAt(l-1);   //

            //

            
            for (int a = 0; a < i - 1; a++)                                                           //  In der verschachtelten For-Schleife werden Objekte der Klasse "Person"
            {                                                                                       //  erstellt und mit Daten aus den Arrays "Datenliste" und "Namenliste", sowie
                for (int b = 0; b < k - 1; b++)                                                     //  mit einer Zufallszahl die das Alter der Person repräsentiert, erstellt
                {
                    for (int c = 0; c < l - 1; c++)
                    {
                        int Personenalter = Alter.Next(0, 80);
                        Personenliste.Add(new Person
                        {
                            Name = Convert.ToString(Namenliste[a]),
                            Städtchen = Convert.ToString(Stadtliste[b]),
                            Alter = Personenalter,
                            Nachname = Convert.ToString(Namenliste2[c])

                        });
                        


                    }
                }

            }
                SequentiellesLinq(Personenliste, Abfrageuhrseq);                             //
                ParallelLinq(Personenliste, Abfrageuhrpar);




                Console.WriteLine(Abfrageuhrseq.ElapsedMilliseconds + "   " + Abfrageuhrpar.ElapsedMilliseconds);
                Console.ReadLine();
      
        }

        public static void SequentiellesLinq(ArrayList Personenliste, Stopwatch Abfrageuhr)                   //  Methode, die eine sequentielle Abfrage durchführt
        {                                                                                                       //
            var query = from Person person in Personenliste                                                     //
                        where person.Städtchen.Contains("b")
                        where person.Name.Contains("Albina") || person.Name.Contains("Aleta") || person.Name.Contains("l")
                        where person.Alter == 45
                        orderby person.Name
                        select person;                                                                    //
            Abfrageuhr.Start();                                                                                 //
            foreach (Person p in query) { }                                                                     //
            Abfrageuhr.Stop();                                                                                  //
        }

        private static void ParallelLinq(ArrayList Personenliste, Stopwatch Abfrageuhrpar)
        {
            var query = from Person person in Personenliste.AsParallel()
                        where person.Städtchen.Contains("b")
                        where person.Name.Contains("Albina") || person.Name.Contains("Aleta") || person.Name.Contains("l")
                        where person.Alter == 45
                        orderby person.Name
                        select person;
            Abfrageuhrpar.Start();
            foreach (Person p in query) { }
            Abfrageuhrpar.Stop();
        }
    }
}


würde ungern allzu umfangreiche optimierungen vornehmen..muss in 2 tagen das teil abgeben und das programm ist nur nebensählich also dient der verdeutlichung
 
Das im Debug modus mehr speicher verbraucht wird ist schonmal klar. aber mal drangedacht das nur im Release modus wirklich optimiert wird? wahrscheins erkennt der compiler bei diesem seltsamen beispiel optimierungsmoeglichkeiten und nutzt diese aus.
 
ähm wäre es nicht einfacher mit der DB zu arbeiten? Datenbanken sind ja dafür da, damit man nicht alle objekte im speicher hält!
 
roker002 schrieb:
ähm wäre es nicht einfacher mit der DB zu arbeiten? Datenbanken sind ja dafür da, damit man nicht alle objekte im speicher hält!

Das denke ich auch, dass eine DB das viel besser kann. Und vor allem kannst du auch LINQ über Linq to SQL oder Linq to Entities benutzen, das die passenden SQL-Abfragen im Hintergrund generiert.
 
ok ich nehme eure ratschläge dankbar an

ich soll aber datenparallelität und dessen umsetzung mithilfe von Plinq betrachten.
mit dem prog versuche ich nur zu zeigen inwiefern die geschwindigkeit mit steigender anzahl von prozessorkernen skaliert. dafür ist linq to objects völlig ausreichend =)

trotzdem danke für all die tipps
 
Wenn die Anforderung der Aufgabe Richtung PLinq geht, ist es natürlich OK, keinen Gedanken an eine Konvertierung der Testdaten von dateibasiert zu datenbankbasiert zu verschwenden. ;-)

Eventuell wäre es aber performancetechnisch günstiger, ein Enumerator zu implementieren, der erst während der Plinq-Abfrage durch die Datei geht anstatt vorher alle Datensätze in den Speicher zu laden...
 
OK ich schätze da kommen wir dem ganzen Problem schon einmal deutlich näher:

1.) Der Code zum Generieren der Testdaten ist nicht ideal. Das kann man etwas eleganter lösen. Ich würde es ca. so schreiben ;-):

Code:
var Random = new Random();
            
            var FrauenNamen = File.ReadAllText("Frauen.txt").Split(new String[]{"\n"},StringSplitOptions.RemoveEmptyEntries);
            var MaennerNamen = File.ReadAllText("Maenner.txt").Split(new String[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
            var StrassenNamen = File.ReadAllText("Strassen.txt").Split(new String[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);

            var Personen = new ArrayList();
            
            foreach(String FrauenName in FrauenNamen)
            {
                foreach (String MaennerName in MaennerNamen)
                {
                    foreach (String StrassenName in StrassenNamen)
                    {
                        int Alter = Random.Next(0, 80);
                        Personen.Add(new Person(FrauenName, MaennerName, StrassenName));
                    }
                }
            }

2.) Nachdem es darum geht, die Vorteile von LINQ aufzuzeigen, ist die Diskussion über Performanceoptimierungen schon einmal hinfällig.

3.) Ich kann mir schon gut vorstellen, warum es im Releasemodus funktioniert.
Grundsätzlich erzeugst du schon einmal viel zu viele Testdaten. Bei 500 Mio. Datensätzen zu je ca. 100 Zeichen würdest du schon 50GB an RAM verbraten. Ein 32Bit Prozess kann nur 2GB nutzen.
Der einzige Grund, warum es trotzdem funktioniert scheint darin zu liegen, dass so gut wie alle Strings doppelt sind. In .NET gibt es für Strings eine Optimierung, dass zwei gleiche Strings immer nur einmal im Speicher abgelegt werden. Das funktioniert deshalb, weil du einen String nicht bearbeiten kannst, sondern nur einen neuen erzeugen.
In deinem Fall brauchen also die Strings so gut wie keinen Platz. Das einzige, was Platz verbraucht ist die ArrayList von Personen.
Pro Person werden zumindest 3 32Bit Pointer auf den jeweiligen Stringwert benötigt, sowie 32Bit für das Alter = 16Byte + etwas Overhead für die Klasse an sich. Je nach Compilereinstellung kann der reale Platzverbrauch eine Klasse bzw. Struktur jedoch etwas schwanken. Bei C Compilern gibt es z.B. die Option die Strukturen auf ein Vielfaches von 16 Byte aufzufüllen. Vielleicht kommt gerade so eine Einstellung hier zu Tragen.

Ich würde an deiner Stelle die Menge der Testdaten etwas reduzieren. Ich denke 1 Mio. Datensätze sollten schon ein ausreichendes Bild abgeben. Dafür würde ich dafür sorgen, dass die Strings in deiner Klasse möglichst eindeutig sind d.h. an alle Frauen- bzw. Männernamen in jedem Schleifendurchlauf noch eine Zufallszahl hinten anhängen. Sonst hat das zur Folge, dass du real nur ein paar Strings verwendest und diese dann sehr schnell im Cache der jeweiligen Kerne landen, was das Bild durch sehr stark verzerrt. Bei großen Datenmengen sollte in erster Linie der RAM limitieren.

4.) Du solltest das Query auch mitstoppen. Vielleicht ist z.B. beim Parallel For schon ein extra Initialisierungsaufwand notwendig.

5.) Kannst du mir deine Testdateien einmal schicken. Dann kann ich schauen, wie groß der Unterschied bei mir ist (Sandy Bridge mit 4 Kernen/8 Threads) bzw. wie schnell ich den Code optimieren kann (bei deiner vorgegebenen ArrayList). Mich würde so ein Vergleich dringend interessieren, vor allem, wenn ich ein paar verschiedene CollectionTypen vergleichen kann (z.B. List(Of T) vs. ArrayList vs. Array)

6.) Kleine Frage an die Spezialisten, die aus dem Array gerne eine Datenbank machen wollen:
Habt ihr schon einmal einen Server für eine Datenbank mit 500 Mio. Datensätzen gesehen? Da tut es ein einfacher Bürorechner schon lange nicht meh. Wenn man da nicht mindestens RAM im dreistelligen GB Bereich reinstopft, ist da ziemlich bald Sense, einmal abgesehen davon ist der Performance beim Durchsuchen auch deutlich schlechter, selbst mit angepasstem Index, da es z.B. für name.contains("1") keinen Index gibt ;-)
 
andr_gin schrieb:
2.) Nachdem es darum geht, die Vorteile von LINQ aufzuzeigen, ist die Diskussion über Performanceoptimierungen schon einmal hinfällig.

Wenn das Programm gar nicht erst läuft, weil die Beispieldaten nicht im Speicher repräsentiert werden können, ist es schon sinnvoll, über Performance nachzudenken. Performance ist nicht nur Geschwindigkeit sondern z.B. auch Speicherverbrauch!

andr_gin schrieb:
3.) Ich kann mir schon gut vorstellen, warum es im Releasemodus funktioniert.
Grundsätzlich erzeugst du schon einmal viel zu viele Testdaten. Bei 500 Mio. Datensätzen zu je ca. 100 Zeichen würdest du schon 50GB an RAM verbraten. Ein 32Bit Prozess kann nur 2GB nutzen.
Der einzige Grund, warum es trotzdem funktioniert scheint darin zu liegen, dass so gut wie alle Strings doppelt sind. In .NET gibt es für Strings eine Optimierung, dass zwei gleiche Strings immer nur einmal im Speicher abgelegt werden. Das funktioniert deshalb, weil du einen String nicht bearbeiten kannst, sondern nur einen neuen erzeugen.

Das passiert NICHT automatisch durch die Laufzeit, sondern nur, wenn explizit für jeden String die Funktion Intern() aufgerufen wird!

andr_gin schrieb:
6.) Kleine Frage an die Spezialisten, die aus dem Array gerne eine Datenbank machen wollen:
Habt ihr schon einmal einen Server für eine Datenbank mit 500 Mio. Datensätzen gesehen? Da tut es ein einfacher Bürorechner schon lange nicht meh. Wenn man da nicht mindestens RAM im dreistelligen GB Bereich reinstopft, ist da ziemlich bald Sense, einmal abgesehen davon ist der Performance beim Durchsuchen auch deutlich schlechter, selbst mit angepasstem Index, da es z.B. für name.contains("1") keinen Index gibt ;-)

Eine Datenbank hat nicht die Aufgabe, 500 Mio Datensätze permanent im Speicher zu halten. Außerdem glaube ich nicht, dass du genau weißt, welche versteckten Feld-Indizierungen zur Abfragenoptimierung das jeweilige DBMS wirklich vornimmt, die auch dein Beispiel extrem schnell ausführbar machen würden.
 
Zurück
Oben