myCSharp.de - DIE C# und .NET Community (https://www.mycsharp.de/wbb2/index.php)
- Entwicklung (https://www.mycsharp.de/wbb2/board.php?boardid=3)
-- Basistechnologien und allgemeine .NET-Klassen (https://www.mycsharp.de/wbb2/board.php?boardid=23)
--- Geschwindigkeit : Speicher Allokierung für String Variablen (https://www.mycsharp.de/wbb2/thread.php?threadid=122173)


Geschrieben von Charly am 11.09.2019 um 11:34:
  Geschwindigkeit : Speicher Allokierung für String Variablen
Hallo,

ich habe hier ein Problem was ich gelöst habe, jedoch interessieren mich dazu andere Möglichkeiten und Meinungen wie man damit umgehen könnte.

Ich lese sehr viele Daten und baue daraus eine CSV Datei die ich wegschreibe. Erster Ansatz war : "Wird im Arbeitsspeicher wohl am schnellsten sein ..." , also lasse ich alle Logikroutinen rüberrattern und erzeuge einen String der die gesamte CSV Datei enthält. Nun ist es wohl so, dass C# den Speicherbedarf für die Variable bei jedem Anhängen von weiteren Daten immer neu alloziert. Dieses hin und her kostet massivst Zeit. Nach 4 Minuten war erst ein Drittel der Verarbeitung durch.

Also habe ich einen Streamwriter genommen und jede Zeile stumpf auf die Festplatte geschrieben. Alle 110.000 Datensätze werden in 1-2 Sekunden geschrieben.

Die Logik dahinter - worüber ich im ersten Ansatz nicht nachgedacht habe - warum das so ist leuchtet mir ein.

Meine Gedanken:

1. Prinzipiell ist RAM schneller als HDD ...
2. Natürlich kann auch mit Dateien arbeiten, aber warum sollte ich gegen die Logik aus 1. arbeiten ?
3. Kann man für die String Variable nicht gleich vorbestimmen wieviel Speicher sie von vornherein allozieren soll ? (Eventuell über den StringBuilder ?)
4. Alloziert man selbst Speicher und schreibt darin mit einem Pointer weg ? (unsafe) der Bedarf könnte vorab Bytegenau berechnet werden - wäre wohl das schnellste denke ich ?
5. Memorystream erzeugen - kann man da eine Speichergröße vorallozieren um nicht unsafe selbst Speicher reservieren zu müssen ?

Nehmen wir mal an dass wir das direkt über den RAM lösen möchten. Über welchen Weg würdet Ihr das lösen ?

Gruß
Charles


Geschrieben von T-Virus am 11.09.2019 um 12:37:
 
Dein Problem dürfte sich beim umstellen von String auf den StringBuilder lösen.
Strings erzeugen bei allen Operationen immer eine neue Instanz mit dem neuen Wert, was beim generieren von einer größeren CSV den Speicher vollmüllt und eben lange dauert.

Beim StringBuilder wird der String erst beim Aufruf von ToString() erzeugt.
Da kannst du munter fröhlich Daten anhängen und beim schreiben der Datei einfach ToString aufrufen.

Nachtrag:
Deine Denkansätze kannst du übrigens durch den StringBuilder verwerfen.
Es gibt sogut wie nie einen Grund String Operationen durch unsafe oder andere Konstrukte laufen zu lassen.
Mal davon abgesehen, dass du damit dann wieder Code schreibst, den dir StringBuilder/String schon bieten.

 StringBuilder

T-Virus


Geschrieben von Charly am 11.09.2019 um 13:02:
 
Ich lasse das mal interesse halber für mich in der Schleife durchlaufen und prüfe die Schnelligkeit der StringBuilder Routine. Wenn er einfach nur "hinten dran" Speicher neu allokiert dürfte das ja kein Problem sein. Das ständig neu Instanzieren geht (natürlich) ganz schön auf die Performance.

Bin gespannt.


**EDIT**

Ich habe nur gefühlt gemessen und nicht in Milisekunden : gefühlt ist der StringBuilder aber ungefähr genau so schnell wie das direkte Schreiben auf Festplatte und damit die beste Methode das Problem zu lösen. Ich könnte jetzt aus dem Gefühl nicht sagen welche Methode schneller wäre - wobei 1-2 Sekunden gegenüber 12 Minuten ein erheblicher Geschwindigkeitsgewinn ist.

C#-Code:
buffer += "DATEN;DATEN;DATEN;DATEN;DATEN;";

ist dahingehend nicht zu empfehlen ...

Wieder was gelernt ... es wird zwar nicht direkt neu instanziert, aber das was im Hintergrund abläuft ist halt nicht performant.


Geschrieben von Th69 am 11.09.2019 um 13:16:
 
Du solltest aber dann dem StringBuilder eine Anfangsgröße mitgeben, damit intern nicht immer wieder neu Speicher alloziert werden muß (das ja auch Performance kostet).
Wenn du in etwa die Länge jeder Datenzeile kennst, dann multipliziere diese mit der Anzahl der Datensätze, um die ungefähre Gesamtgröße vorab zu bestimmen (du kannst ja auch noch einen kleinen Puffer draufaddieren).

Es spricht aber nichts dagegen, die Daten auch gleich auf Platte zu schreiben (außer dein Programm arbeitet nachher noch mit den Daten weiter und müßte diese dann erst wieder laden).


Geschrieben von T-Virus am 11.09.2019 um 13:53:
 
@Charly
Wie verwendest du den aktuell den StringBuilder?
Hier solltest du z.B. auch mit AppendFormat arbeiten, wenn du die Werte im StringBuilder einfügst.
Wenn du auch hier wieder den String beim Append/AppendLine mit + verknüpfst, werden auch wieder einzelne String Instanzen erzeugt bei jeder Zeile.
Auch das kannst du vermutlich noch weg elimieren.

Dann dürftest du auch die performanteste Umsetzung haben.
Das wegschreiben, kannst du dann auch nicht mehr optimieren.

T-Virus


Geschrieben von Charly am 11.09.2019 um 14:16:
 
Eine Anfangsgröße hatte ich nicht mitgegeben ; das könnte das ganze noch etwas performanter machen ; aber prinzipiell war das schon ok. Ich habe den Datensatz mit .Append einfach hinzugefügt.

Danke euch für den Austausch ...


Geschrieben von gfoidl am 11.09.2019 um 22:27:
 
Hallo Charly,

Zitat:
bei jedem Anhängen von weiteren Daten immer neu alloziert

siehe  [FAQ] Besonderheiten der String-Klasse (immutabler Referenztyp mit Wertsemantik)

Zitat:
Kann man für die String Variable nicht gleich vorbestimmen wieviel Speicher sie von vornherein allozieren soll ?

Direkt: Ab .NET Core 2.1 mittels  string.Create. Achtung: die Länge muss genau angegeben werden, da so direkt in den Speicher vom (dann) fertigen String geschrieben wird.

Indirekt: via StringBuilder wie hier schon vorgeschlagen.

Zitat:
Alloziert man selbst Speicher und schreibt darin mit einem Pointer weg ? (unsafe) der Bedarf könnte vorab Bytegenau berechnet werden - wäre wohl das schnellste denke ich ?

Nicht nötig, umständlich und fehleranfällig.

Zitat:
Memorystream erzeugen - kann man da eine Speichergröße vorallozieren um nicht unsafe selbst Speicher reservieren zu müssen ?

Ja, siehe Bitte schau in die SDK-/MSDN-Doku

Zitat:
Wenn er einfach nur "hinten dran" Speicher neu allokiert dürfte das ja kein Problem sein.

Im StringBuilder gibt es einen Buffer für die Zeichen des zu erstellenden Strings. Wenn der Buffer durch die Appends voll wird, so wird ein doppelt so großer Buffer* erstellt, der Inhalt des bisherrigen Buffers in den neuen Buffer kopiert und der bisherige Buffer verworfen (bis in der GC abräumt).
Daher auch der Hinweis von Th69: Anfängsgröße bekanntgeben, das spart Buffer-Wachstum wie vorhin beschrieben.

* kann sein dass sich das geändert hat und eine andere Wachstumsrate verwendet wird, das ändert aber nichts an der Aussage

Zitat:
Es spricht aber nichts dagegen, die Daten auch gleich auf Platte zu schreiben

Daumen hoch
Das sollte auch gemacht werden, denn es bringt nichts zuerst im RAM einen großen String zusammenzubauen, der dann fürs Schreiben in die Datei kodiert (z.B. UTF-cool werden muss um dann in den Datei-Buffer zu schreiben, ...
Das kann auch direkt geschrieben werden, z.B. mittels StreamWriter der auch Methoden fürs formatierte Schreiben bietet.

Dann gibt es auch keinen Riesenstring der vermutlich im LOH (large object heap) landet (falls er mehr als 85e3 bytes hat) und erst mit einer Gen2-Collection des GC abgeräumt wird -- also sehr selten.
Beim direkten Schreiben können zwar mehrere Gen0-Collections vom GC auftreten, aber die sind nicht schlimm.


Zitat:
Dann dürftest du auch die performanteste Umsetzung haben.
Das wegschreiben, kannst du dann auch nicht mehr optimieren.

Ich fürchte das geht jetzt zuweit, aber der Vollständigkeithalber:
Vermutlich geht es mit IO.Pipelines noch performanter und v.a. mit weniger Allokationen.
ABER: der Code wird dadurch aufwändiger und umständlicher (= weniger wartbar). Sollte es keine Server-Lösung od. keine zwingende Forderung nach schnellstem Code geben, so soll wartbarer Code bevorzugt werden und keine Suche nach ultimativer Performance gestartet werden.


Ich würde mit dem StreamWriter direkt schreiben. Ist einfach umzusetzen, performant und der Code leserlich.

mfG Gü


Geschrieben von LaTino am 12.09.2019 um 08:03:
 
Übrigens ist bei deiner ursprünglichen Variante nicht die Speicherallokation teuer, sondern das kopieren der Strings, das notwendig ist, weil string nun einmal immutable ist.

LaTino


Geschrieben von gfoidl am 12.09.2019 um 13:34:
 
Hallo,

Zitat:
nicht die Speicherallokation teuer

Das ist als generelle Antwort / Hinweis zu sehen.

Speicherallokationen auf dem Heap (per new od. implizit wie beim Kopieren vom String) sind in .NET nicht aufwändig. Im Grunde wird vom GC nur ein Pointer verschoben und der so allozierte Speicher genullt (aufgrund von ".NET safety"). Das geht schnell.
Aufwändig hingegen ist das Abräumen vom nicht mehr benötigten Speicher durch den GC -- das sammeln und kompaktieren vom Speichern und ev. das verschieben der Objekte in die nächste Generation.
D.h. wenn Speicherallokaitonen vermieden werden können, so erspart man dem GC eine Menge Arbeit und die Anwendung wird insgesamt schneller. Bemerkbar vllt. nicht unbedingt bei Konsolenanwendungen die nur ein Job ausführen und sich beenden, sondern v.a. bei Server-Anwendungen u.ä. Anwendungen die "lange" laufen.

Speicherallokationen können oft vermieden werden durchmfG Gü


© Copyright 2003-2019 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 15.09.2019 19:07