Frage

Ich bin in einer großen Textdatei mit 1,4 Millionen Zeilen zu lesen, die 24 MB groß sind (durchschnittlich 17 Zeichen eine Zeile).

Ich bin mit Delphi 2009 und die Datei ist ANSI aber in Unicode beim Lesen umgewandelt wird, so ziemlich können Sie den Text einmal sagen, 48 MB in Größe umgewandelt wird.

(Edit: Ich fand ein viel einfacheres Beispiel ...)

ich diesen Text in einem einfachen String Laden:

  AllLines := TStringList.Create;
  AllLines.LoadFromFile(Filename);

Ich fand, dass die Linien von Daten wesentlich mehr Speicher zu nehmen scheinen, als ihre 48 MB.

In der Tat, verwenden sie 155 MB Speicher.

Ich habe nichts dagegen Delphi mit 48 MB oder sogar weniger als 60 MB für einigen Speicherverwaltungsaufwand ermöglicht. Aber 155 MB übertrieben scheint.

Dies ist kein Fehler von String. Ich habe versucht, vorher die Linien in eine Satzstruktur geladen, und ich habe das gleiche Ergebnis (160 MB).

Ich sehe oder nicht verstehen, was Delphi verursachen könnte oder die FastMM Speichermanager 3-fache Menge an Speicher zu verwenden, notwendig, um die Saiten zu speichern. Heapzuordnung kann nicht sein, dass ineffizient, kann es?

Ich habe das debuggt und erforschen es so weit wie ich kann. Irgendwelche Ideen, warum dies passiert sein könnte, oder Ideen, die mir die übermäßige Nutzung reduzieren könnte helfen würde sehr geschätzt werden.

Hinweis: Ich bin mit dieser „kleineren“ Datei als Beispiel. Ich versuche wirklich eine 320 MB-Datei zu laden, aber Delphi für mehr als 2 GB RAM verlangt und weil diese überschüssige String Anforderung aus dem Speicher ausgeführt wird.

Addenum: Marco Cantu kam gerade heraus mit ein Weißbuch über Delphi und Unicode . Delphi 2009 hat den Overhead pro Strang von 8 Bytes auf 12 Bytes (plus vielleicht 4 mehr für die aktuellen Zeiger auf den String) erhöht. Eine zusätzliche 16 Bytes pro 17x2 = 34-Byte-Zeile fügt fast 50%. Aber ich sehe mehr als 200% Overhead. Was könnte das zusätzliche 150% sein?


Erfolg !! Dank Ihnen allen für Ihre Anregungen. Sie alle haben mich zum Nachdenken. Aber ich werde Jan geben müssen Goyvaerts Kredit für die Antwort, da er fragte:

  

... warum sind Sie TStringList? Muss die Datei wirklich im Speicher als separate Leitungen gespeichert werden?

Das führte mich zu der Lösung, die anstelle der 24 MB-Datei als 1,4 Millionen Linie der Laden String, kann ich meine Gruppe Linien in natürliche Gruppen mein Programm kennt. So führte dies zu 127.000 Zeilen in die Stringliste geladen.

Jetzt jeder Zeile mittelt 190 Zeichen anstelle von 17. Der Aufwand pro String Linie ist die gleiche, aber jetzt gibt es viele weniger Zeilen.

Als ich die 320 MB-Datei anwenden, es läuft nicht mehr aus dem Speicher und lädt nun in weniger als 1 GB RAM. (Und es dauert nur etwa 10 Sekunden zu laden, was ziemlich gut ist!)

Es wird eine wenig zusätzliche Verarbeitung wird die gruppierten Linien zu analysieren, aber es sollte in Echtzeitverarbeitung von jeder Gruppe nicht erkennbar sein.

(Falls Sie sich wundern, ist dies ein Genealogie-Programm, und dies kann der letzte Schritt sein, die ich brauchte, damit es über eine Million Menschen in einem 32-Bit-Adressraum in weniger als 30 Sekunden, um alle Daten zu laden. so habe ich immer noch einen 20 Sekunden-Puffer mit zu spielen, die Indizes in die Daten hinzuzufügen, die Anzeige und Bearbeitung der Daten zu ermöglichen, benötigt werden.)

War es hilfreich?

Lösung

Sie fragte mich persönlich hier Ihre Frage zu beantworten. Ich weiß nicht, den genauen Grund, warum Sie so hohe Speichernutzung sind zu sehen, aber Sie müssen bedenken, dass TStringList viel mehr tut, als Sie Ihre Datei einfach laden. Jeder dieser Schritte erfordert Speicher, der in Speicherfragmentierung führen. TStringList braucht Ihre Datei in den Speicher zu laden, konvertiert sie von Ansi in Unicode, spaltete es in eine Zeichenfolge für jede Zeile, und stopfen diese Zeilen in einem Array, das viele Male neu zugewiesen werden.

Meine Frage an Sie ist, warum verwenden Sie TStringList? Muss die Datei wirklich im Speicher als separate Leitungen gespeichert werden? Werden Sie die Datei im Speicher zu ändern, oder auch nur Teile davon angezeigt werden? Halten Sie die Datei im Speicher als ein großes Stück und scannen das Ganze mit regulären Ausdrücken, die die Teile, die Sie passen mehr Speicher effizient sein wollen als separate Leitungen zu speichern.

Außerdem muss die gesamte Datei in Unicode konvertiert werden? Während der Anwendung Unicode ist, ist Ihre Datei Ansi. Meine allgemeine Empfehlung ist Ansi Eingang Unicode so schnell wie möglich umzusetzen, da dies zu CPU-Zyklen spart. Aber wenn Sie 320 MB Ansi Daten, die als Ansi Daten bleiben wird, wird der Speicherverbrauch der Engpass sein. Versuchen Sie die Datei als Ansi im Speicher zu halten, und wandeln nur die Teile, Sie werden dem Benutzer als Ansi werden angezeigt wird.

Wenn die 320 MB-Datei nicht eine Datendatei, die Sie von bestimmten Informationen sind zu extrahieren, aber ein Datensatz Sie ändern möchten, sollten Sie es in eine relationale Datenbank konvertieren, und lassen Sie die Datenbank-Engine Sorge, wie die riesige verwalten Satz von Daten mit eingeschränktem RAM.

Andere Tipps

Was ist, wenn Sie Ihre ursprüngliche Akte Verwendung Ansi gemacht? Das hackt es in der Hälfte sofort? Nur weil Delphi standardmäßig UnicodeString- bedeutet nicht, Sie es zu benutzen.

kurze Strings zu verwenden, selbst und abrasieren ein paar Bytes

Außerdem, wenn Sie genau wissen, die Länge der einzelnen Strings (innerhalb eines Zeichens oder zwei), dann könnte es besser sein.

Ich bin neugierig, ob es vielleicht ein besserer Weg zu erreichen, was Sie zu tun versuchen. 320 MB Text in den Speicher geladen ist vielleicht nicht die beste Lösung sein, auch wenn Sie es nach unten nur bekommen kann auf 320 MB erfordern

  

ich mit Delphi 2009 und die Datei ist ANSI aber in Unicode beim Lesen umgewandelt wird, so ziemlich können Sie den Text einmal sagen, 48 MB in Größe umgewandelt wird.

Sorry, aber ich verstehe das nicht überhaupt. Wenn Sie eine Notwendigkeit für Ihr Programm haben Unicode zu sein, doch die Datei „ANSI“ zu sein (es muss etwas Zeichensatz hat, wie WIN1252 oder ISO8859_1) ist nicht das Richtige. Ich würde zuerst konvertieren es UTF8 zu sein. Wenn die Datei enthält keine Zeichen> = 128 wird es nichts ändern (es wird auch gleich groß sein), aber Sie sind für die Zukunft gerüstet.

Jetzt können Sie es in UTF-8-Strings laden, die Ihren Speicherverbrauch nicht verdoppeln. On-the-fly-Konvertierung der wenigen Strings, die auf dem Bildschirm zur gleichen Zeit auf der Delphi-Unicode-String ist langsamer sichtbar sein können, aber angesichts der kleineren Speicherbedarf Ihr Programm ausführen viel besser auf Systemen mit wenig (kostenlos) Speicher.

Nun, wenn Ihr Programm noch mit TStringList zu viel Speicher verbraucht kann man immer TStrings oder sogar IStrings in ihr Programm, und eine Klasse schreiben, die IStrings oder erbt TStrings implementiert und hält sich nicht alle Linien im Speicher. Einige Ideen, die in den Sinn kommen:

  1. Lesen Sie die Datei in ein TMemoryStream, und halten ein Array von Zeigern auf die ersten Zeichen der Linien. einen String zurückkehrend ist einfach dann, Sie eine richtige Zeichenfolge zwischen dem Anfang der Zeile und dem Beginn der nächsten, mit der Tschechischen Republik und NL gestrippt zurückzukehren nur benötigen.

  2. Wenn dies immer noch zu viel Speicher verbraucht, ersetzen Sie die TMemoryStream mit einem TFileStream, und nicht ein Array von char Zeiger beibehalten, aber eine Reihe von Datei-Offsets für die Zeile beginnt.

  3. Sie können auch die Windows-API-Funktionen für Memory-Mapped-Dateien verwenden. Das ermöglicht es Ihnen, mit Speicheradressen statt Datei-Offsets zu arbeiten, aber nicht verbrauchen so viel Speicher wie die erste Idee.

In der Standardeinstellung Delphi 2009 die TStringList liest eine Datei als ANSI, es sei denn, ein Byte Order Mark ist die Datei als etwas anderes zu identifizieren, oder wenn Sie eine Codierung als optionale zweite Parameter von Loadfromfile zur Verfügung stellen.

Also, wenn Sie sehen, dass der TStringList mehr Speicher Aufnahme ist, als man denkt, dann etwas anderes vor sich geht.

Sind Sie zufällig das Programm mit FastMM Quellen von Source kompilieren und mit FullDebugMode definiert? In diesem Fall wird FastMM nicht wirklich die Freigabe ungenutzte Speicherblöcke, die das Problem erklären würde.

Sind Sie auf Windows angewiesen, Ihnen zu sagen, wie viel Speicher das Programm verwendet? Es ist berüchtigt für den Speicher durch eine Delphi App verwendet overstating.

ich viel zusätzlichen Speichernutzung in Ihrem Code sehen, though.

Ihre Satzstruktur ist 20 Bytes - wenn es ein solcher Datensatz pro Zeile ist sind Sie für die Datensätze als für den Text bei mehr Daten suchen

.

Darüber hinaus hat ein String eine inhärente 4-Byte-Overhead -. Weitere 25%

Ich glaube, es gibt eine gewisse Zuordnung Granularität in Delphi Heap Handhabung, aber ich kann mich nicht erinnern, was es derzeit ist. Selbst bei 8 Byte (zwei Zeigern für eine verkettete Liste von freien Blöcken) Sie suchen in einem anderen 25%.

Beachten Sie, dass wir bereits bis zu über eine Steigerung von 150%.

Ein Teil davon könnte der Blockzuordnungsalgorithmus sein. Wie Sie Ihre Liste wächst, beginnt er die Größe des Speichers an jedem Chunk zugeordnet erhöht. Ich habe nicht in eine lange Zeit sah, aber ich glaube, dass es so etwas wie eine Verdoppelung der Menge der letzten zugeteilt jedes Mal geht es über genügend Arbeitsspeicher ausgeführt. Wenn Sie beginnen, mit Listen umgehen, dass große, Ihre Zuweisungen sind auch viel größer, als Sie schließlich benötigen.

EDIT - Wie lkessler wies darauf hin, ist dieser Anstieg tatsächlich nur 25%, aber es sollte immer noch als Teil des Problems in Betracht gezogen werden. wenn Ihr nur über den Kipppunkt, könnte es eine enorme Speicherblock in die Liste zugeordnet sein, die nicht verwendet wird.

Warum laden Sie diese Menge an Daten in eine TStringList? Die Liste selbst wird einige Overhead haben. Vielleicht haben Sie TTextReader könnte helfen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top