Laden...

Einlesen großer CSV Dateien

Erstellt von janDD vor 11 Jahren Letzter Beitrag vor 11 Jahren 19.406 Views
Thema geschlossen
J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren
Einlesen großer CSV Dateien

verwendetes Datenbanksystem: <CSV>

Hallo,
ich scheitere beim Einlesen großer CSV Dateien in eine DataTable an einer OutOfMemoryException (Methode getDataTableFromCSV1).

Das Anlegen einer wesentlich größeren DataTable funktioniert aber (Methode generateDataTable). Die Strings in dem CSV sind übrigens deutlich kleiner als der test-string.

Ich habe schon allerhand gelesen, dass die String.Split()-Methode das Problem sein könnte, aber ich weiß nicht, wie ich diese umgehen könnte ...

Ich habe auch schon den LUMEN CSV Parser verwendet - auch hier ein OutOfMemory.

Bevor jemand fragt: Ja, ich brauche alle Daten (um Statistiken zu berechnen - ANOVAs etc).

Danke und Gruß,

Jan

 

using System.IO;
using System.Text;
using System.Data;

namespace CSVImport
{
    public class Class1
    {

        static void Main(string[] args)
        {

           
            // works 15000 columns x 15000 rows, process takes around 1.2gb
            generateDataTable();

            // out of memory, only 9000 columns x 12000 rows, process > 1.8gb
            getDataTableFromCSV("C:\test.csv", bool verbose)

         }

    }

   private static void generateDataTable()
        {

            TimeSpan tStart = (DateTime.UtcNow - new DateTime(1970, 1, 1));

            DataTable dt = new DataTable(); 
            for (int i = 0; i < 15000; i++)
            {
                dt.Columns.Add(new DataColumn("column "+i.ToString(),typeof(string)));
            }

            for (int j=0; j<15000; j++){
                
                DataRow r = dt.NewRow();
                for (int i = 0; i < 15000; i++)
                {

                    r[i] = "this is a relatively long string to test memory for a datatable";
   
                }
                dt.Rows.Add(r);
            }

            TimeSpan tNow = (DateTime.UtcNow - new DateTime(1970, 1, 1));
            if (true)
            {
                Console.WriteLine(tNow.TotalSeconds - tStart.TotalSeconds);
            }


            Console.WriteLine("Columns: " + dt.Columns.Count.ToString());
            Console.WriteLine("Rows: " + dt.Rows.Count.ToString());


        }

       public static DataTable getDataTableFromCSV(string path, bool verbose){

            DataTable dt = new DataTable(path);
            TimeSpan tStart = (DateTime.UtcNow - new DateTime(1970, 1, 1));

            try
            {
                using (StreamReader readFile = new StreamReader(@path))
                {
                    string line;
                    string[] row;
                    bool isFirstLine = true;

                    while ((line = readFile.ReadLine()) != null)
                    {
                        row = line.Split(',');
                        if (isFirstLine)
                        {
                            foreach (string column in row)
                            {
                                dt.Columns.Add(column);
                              }
                          isFirstLine = false;
                        }
                        else
                        {

                            DataRow r = dt.NewRow();
                            for (int i = 0; i < row.Length; i++)
                            {
                                r[i] = row[i];
                            }
                            
                           dt.Rows.Add(r);
                           
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            TimeSpan tNow = (DateTime.UtcNow - new DateTime(1970, 1, 1));
            if (verbose)
            {
                Console.WriteLine(tNow.TotalSeconds - tStart.TotalSeconds);
            }

            return dt;

        }
}

 
5.657 Beiträge seit 2006
vor 11 Jahren

Hi janDD,

was genau sind bei dir "große" Dateien? Wenn die Datenmenge größer ist als der verfügbare Speicher, kannst du halt nichts machen. In dem Fall würde ich dir raten, die Daten zuerst in eine Datenbank zu importieren, und sie dort weiterzuverarbeiten.

Christian

Weeks of programming can save you hours of planning

C
224 Beiträge seit 2009
vor 11 Jahren

Eine Split Funktion über 9000 columns liefert ein großes Array zurück (wobei die Größe im Auge des Betrachters liegt). Auch hier kann man Speicherplatz sparen. Statt ein Array könnte man hier eine Schleife durchlaufen lassen, welche immer nur den nächsten Feldinhalt verarbeitet.

Pseudocode:
..für jedes Zeichen
..{
....Sammel Zeichen bis Trennzeichen oder Ende als Feldinhalt
....Wenn Feldinhalt vollständig gesammelt:
....{
......verarbeite Feldinhalt
....}
..}

Gruß, CoLo

J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren

Hi,

die Größe der Datei ist 1gb. Die Strings sind aber kürzer als mein Teststring "this is a relatively long string to test memory for a datatable" und die CSV-Datei hat weniger Spalten/Zeilen als meine künstlich erstellte DataTable -- daran kann's also nicht liegen.

Grüße,

C
224 Beiträge seit 2009
vor 11 Jahren

Evtl. stürzt er ja schon bei ReadLine ab.
Wo stürzt er denn ab, kannst Du das ermitteln?

J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren

Ja, er stürzt beim ReadLine ab ... aber das ist m.E. Zufall --- es ist halt die erste Funktion, die nicht mehr genug Speicher bekommt...

Ich hab es auch mal Deinen Vorschlag verwendet, den String zeichenweise zu durchlaufen ... gleiches Problem

Grüße,
Jan

C
224 Beiträge seit 2009
vor 11 Jahren

Probier mal, ob er beim ReadLine abstürzt, wenn Du quasi alles nach Readline auskommentierst.

Gruß, CoLo

F
10.010 Beiträge seit 2004
vor 11 Jahren

Natürlich stürzt die Anwendung mit OutOfMemory ab.

1GB Datei bedeutet schon mal bei einlesen nach UniCode ( intern ist jeder sting Unicode also 16Bit) das mindestens 2GB im Speicher stehen würden.
Dann noch die Verwaltungsinfos der DataTable und schon bist du weit über dem was man benutzen kann.

Warum meinst du so eine riesige Datei komplett einlesen zu müssen?

J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren

Nein, es ist eben nicht "natürlich", dass er abstürzt. Meine Methode 1 läuft problemlos.
MaW, ich kann problemlos eine DataTable mit 15000 Spalten und 15000 Zeilen anlegen, die den teststring "this is a relatively long string to test memory for a datatable" enthalten.

Die Kombination aus einlesen + datatable anlegen ist der knackpunkt. Aber mir ist nicht klar wo, denn schließlich liest der Streamreader ja immer nur eine Zeile. Scheinbar räumt der Garbage Collector die Zeilen nicht vernünftig wieder weg oder der Streamreader ist buggy ... k.a.

Und ja ... ich brauche die große Datei (um verschiedene Statistiken zu berechnen, Stichwort ANOVA)

@CoLo: wenn ich alles auskommentiere, läuft es durch. Es läuft sogar durch, wenn ich statt


r[i] = row[i];

folgendes schreibe


r[i] = "this is a relatively long string to test memory for a datatable";

Das ist mir völlig uklar. Denn auch hier rattert der Streamreader über alle Zeilen...
Mhmm..

Grüße,
Jan

W
872 Beiträge seit 2005
vor 11 Jahren

Der Optimizer der CLR wird die String Konstante ueberall als gleichen Zeiger auf den festen Wert in ein Array einsetzten und nicht blind in den Speicher schreiben.
Kannst ja mal Zufalls-Strings mit einer festen Laenge testen...
FZelle hat schon recht - Dir wird nur eventuell 64 Bit und ein grosser freier Speicher >4 GB helfen...

F
10.010 Beiträge seit 2004
vor 11 Jahren

@janDD:
Da leigst du aber falsch.
Strings in .NET sind Immutable, aber da Du ja immer den selben ( nicht den gleichen ) String verwendest hast du danach nicht 22.500.000 Strings im Speicher.

C
224 Beiträge seit 2009
vor 11 Jahren

Du brauchst alle Daten. Die Frage ist, brauchst Du sie alle sofort.

Evtl reicht Dir ja auch, wenn Du alle Daten scheibchenweise wegschreibst und scheibchenweise wiedereinliest (MrSparkles Idee)?. So dass Du als Ziel immer nur einen Datensatz statt alle im Speicher hast.

J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren

Hm, verstehe.
die datatable übergebe ich normalerweise bestimmten funktionen, die die berechnungen anstellen. das zeilenweise zu machen, wäre ein riesiger aufwand ...

gibt es denn vllt. eine möglichkeit, die datatable auf platte zu swappen, sobald sie aus dem ram läuft?

5.657 Beiträge seit 2006
vor 11 Jahren

In dem Fall würde ich dir raten, die Daten zuerst in eine Datenbank zu importieren, und sie dort weiterzuverarbeiten.
Christian

Weeks of programming can save you hours of planning

J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren

eine datenbank, die tabellen mit mehr als 10000 spalten erlaubt, ist mir gar nicht bekannt ...

C
224 Beiträge seit 2009
vor 11 Jahren

Kannst ja 10000 Zeilen daraus machen xD. Was beinhalten die Spalten denn?

J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren

Prozessdaten...
Ja sicher kann ich 10000 Zeilen draus machen ... und aus den 12000 Zeilen dann 12000 Spalten?
Ja, ich könnte die Tabellen auch normalisieren ... aber um meine Analyse zu machen, muss ich sie wieder zusammenführen ...

Es gibt in C# ja sowas wie memory mapped files ... kann ich sowas vllt. für meine DataTable nutzen ... ?

5.657 Beiträge seit 2006
vor 11 Jahren

Es gibt auch die BufferedStream-Klasse für solche Zwecke. Aber das nützt dir ja nix, weil du ja wohl trotzdem sämtliche Daten im Speicher brauchst, um damit zu arbeiten.
Eine Datenbank zu verwenden war mein Vorschlag, weil eine Datenbank genau für solche Zwecke entwickelt wurde. Wie du deine Daten in die DB reinbekommst, ist dein Problem. Du könntest die Spalten auf mehrere Tabellen aufteilen oder ein gescheites DB-Modell daraus entwickeln.
Christian

Weeks of programming can save you hours of planning

C
224 Beiträge seit 2009
vor 11 Jahren

Sei kreativ =). Angenommen Du hast 1 Tabelle mit 2 Datensätze mit Inhalt:

tabSatz:
1a,1b,1c
2a,2b,2c

Dann könntest Du z.B. daraus 2 Tabellen machen:

_tabSatz:
ID1
ID2

tabSatzdaten:
ID1, 1a
ID1, 1b
ID1, 1c
ID2, 2a
ID2, 2b
ID2, 2c_

J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren

or leute ... ich hab schon mehrere schemata designed in allen möglichen normalformen.
mein problem ist, dass ich sehr sehr umfangreiche berechnungen mit der datatable anstelle und dafür sind schon alle methoden fertig (>20000 Zeilen code!) und mit kleineren files in benutzung, getestet usw. usf.
die möchte ich gern verwenden, ohne ein halbes jahr mich mit irgendwelchen neuen sql schnipseln rumzuärgern.

daher würde ich gern irgendwie (und wenn es noch so absurd ist) meine daten in eine datatable haben ...

ist das sooooo unverständlich?

aber vielleicht gibt es ja wirklich keinen weg. aber bitte nicht mehr eine datenbank vorschlagen ...

jan

C
224 Beiträge seit 2009
vor 11 Jahren

r[i] = double.Parse(row[i]);
//oder
r[i] = readFile.Position + ";" + i + ";" + row[i].Length;

5.657 Beiträge seit 2006
vor 11 Jahren

or leute ...daher würde ich gern irgendwie (und wenn es noch so absurd ist) meine daten in eine datatable haben ...
ist das sooooo unverständlich?
aber bitte nicht mehr eine datenbank vorschlagen ...

Du kannst mit den Füßen aufstampfen, oder sonstwas machen. Rumquengeln wird dir aber nicht viel Erfolg bringen. Wenn du die Ratschläge nicht annehmen willst, die dir gegeben werden, solltest du ein anderes Argument vorbringen als "or Leute".

Christian

Weeks of programming can save you hours of planning

C
224 Beiträge seit 2009
vor 11 Jahren

Angenommen Du schaffst es eine 2Gb Tabelle im Memory aufzubauen, dann wird es evtl. nur das Problem verlagern, so dass es dann in Deiner Berechnung knallt. X(

W
872 Beiträge seit 2005
vor 11 Jahren

Da Du ADO.Net (System.Data) benutzt, sollte der Unterschied zwischen einer Datenbank, die einen ADO.NET Treiber hat und einer Csv-Datei nicht gross sein.

F
10.010 Beiträge seit 2004
vor 11 Jahren

@janDD:
Berechnungen?
Und warum speicherst du das dann als strings, wenn es doch zahlen sind?

J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren

Hallo,

ja, schon klar. Aber ich sagte doch mehrfach, dass ich die Datenbank-Alternative kenne, aber die Umsatz enormen Aufwand benötigt. Das hat nichts mit aufstampfen oder "die Vorschläge nicht umsetzen wollen" zu tun.

Ich habe eben in diversen Beiträgen im Netz gelesen, dass man mit Hilfe von memory mapped files solcher Speicherprobleme herr werden kann, aber kein Beispiel gefunden.

das wort "berechnungen" steht in diesem zusammenhang für die unterschiedlichsten analysen stichwort kontingenzanalyse, commonality analysen, kategoriale logistische korrelation ...

man kann nicht nur mit zahlen rechnen, wobei natürlich auch zahlen in der datatable stehen ...

F
10.010 Beiträge seit 2004
vor 11 Jahren

Nein Rechnen kann man nur mit Zahlen.
Man kann aber Daten Analysieren.

Dir bleibt nichts anderes Übrig als mal einen Schritt zurück zu machen und zu überlegen ob es wirklich sinnvoll ist weiterhin mit dem Kopf gegen die Wand zu rennen oder ab man nicht eher einen Weg um die Mauer findet.

  1. Was sind das für Daten die mehrere Tausend Spalten benötigen?
  2. Warum bestehst du auf DataTable? Die ist ein Speicherverschwender.
  3. Wenn du dann einen wirklich geeigneten Datentyp gefunden hast, was lässt sich beim einlesen vereinfachen/zusammenfassen?
C
224 Beiträge seit 2009
vor 11 Jahren

man kann nicht nur mit zahlen rechnen Was meinst Du damit? Sind das Funktionen?
Gruß, CoLo

6.911 Beiträge seit 2009
vor 11 Jahren

Hallo janDD,

ich schließe mich den anderen an, dass es günstigere Alternativen gibt, begonnen mit einem vernüftigen Design, das von sich auch schon Herr der Lage ist.
Dieser Punkt wurde aber wohl schon ausreichend diskutiert, so dass dies nicht erneut durchgekaut werden muss.

memory mapped files ..., aber kein Beispiel gefunden. Memory Mapped File Quirks
Ob und wie das bei einer CSV-Datei helfen soll weiß ich nicht. Ich denke Memory Mapped Files sind hier der falsche Weg.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

J
janDD Themenstarter:in
10 Beiträge seit 2012
vor 11 Jahren

man kann mit jedem mathematischen objekt rechnen, zum beispiel mit differentialformen, sogar mit objekten die man gar nicht wirklich hinschreiben kann (zum beispiel nicht riemann-integrierbare funktionen).

deine fragen sind natürlich alle richtig. würde ich das system von grund auf designen würde ich es nicht so machen. aber da ich schon sehr viel code (übernommen) habe, der eine datatable erfordert, würde ich ihn eben gern nachnutzen ...

aber danke für deine hilfe. ich würde den thread hiermit mal schließen...

@CoLo ... ein Beispiel habe ich ja schon des öfteren genannt: ANOVA

R
103 Beiträge seit 2009
vor 11 Jahren

Wenn Du ein 64 Bit Betriebsystem vorraussetzen kannst und die Anwendung auch als 64 Bit oder AnyCPU Compilierst, könnte dein Programm auch ohne Änderung des Codes funktionieren. Windows swappt ja schon von sich aus Daten bei Hauptspeichermangel auf die Swap Datei (also Festplatte oder besser SSD) aus.

Du scheiterst in deinem Fall also wahrscheinlich am 2gb Prozess Speicher Limit und das lässt sich am einfachsten mit einem 64 Bit Prozess umgehen.

Habe das auch schon mal bei einer Auftragsarbeit gemacht. Klar hätte man aufwendig das Programm umschreiben können. In dem Fall war es aber wesentlich günstiger einfach einen neuen 64 Bit Rechner abzustellen, der das erledigt.

6.911 Beiträge seit 2009
vor 11 Jahren

Hallo rasepretrep,

grundsätzlich richtig, wenn .net nicht die Limitierung von 2GB pro Objekt hat und janDD auf das DataTable fixiert ist, wird das dennoch nicht klappen.*

Die Limitierung kann ab .net 4.5 per <gcAllowVeryLargeObjects> aufgehoben werden.

mfG Gü

* ich bin mir nicht ganz sicher ob das DataTable als ein Objekt gehandhabt wird od. nicht, aber davon gehe ich schon aus.

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

R
103 Beiträge seit 2009
vor 11 Jahren

@gfoidl:

Glaube nicht, dass das wirklich ein Problem darstellt, da bei einem Referenztypen in einem C# object nur platz für einen Zeiger im Objekt reserviert werden muss.

Aber ich lasse mich natürlich gerne eines besseren belehren ... 😉

6.911 Beiträge seit 2009
vor 11 Jahren

Hallo rasepretrep,

und wo liegt das referenzierte Objekte? Im Heap - und dort gibt es vom GC her das Limit mit 2GB pro Objekt (das mit <gcAllowVeryLargeObjects> ab .net 4.5 aber aufgehoben werden kann).
Das kannst du ja mal selber einfach ausprobieren. Erstell einfach ein groß genuges Array und Peng.

Edit 29.09.2012: rasepretrep hat mich freundlicherweise per PM darauf hingewiesen, dass meine Antwort missverständlich ist, da es hier um DataTable und nicht um ein Objekt bestehend aus Value-Types geht. Insofern ist sein obiger Einwand als korrekt anzusehen.

Das Thema ist jetzt ohnehin schon lang genug und bevor es auch noch aus dem Leim geht sollten wir es hier wirklich beenden.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Thema geschlossen