myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Entwicklung » Basistechnologien und allgemeine .NET-Klassen » Geschwindigkeit : Speicher Allokierung für String Variablen
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

Geschwindigkeit : Speicher Allokierung für String Variablen

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
Charly Charly ist männlich
myCSharp.de-Mitglied

Dabei seit: 12.03.2014
Beiträge: 29


Charly ist offline

Geschwindigkeit : Speicher Allokierung für String Variablen

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
11.09.2019 11:34 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
T-Virus T-Virus ist männlich
myCSharp.de-Mitglied

Dabei seit: 17.04.2008
Beiträge: 1.284
Entwicklungsumgebung: Visual Studio, Codeblocks, Edi
Herkunft: Nordhausen, Nörten-Hardenberg


T-Virus ist offline Füge T-Virus Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von T-Virus am 11.09.2019 12:41.

11.09.2019 12:37 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Charly Charly ist männlich
myCSharp.de-Mitglied

Dabei seit: 12.03.2014
Beiträge: 29

Themenstarter Thema begonnen von Charly

Charly ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Charly am 11.09.2019 13:07.

11.09.2019 13:02 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Th69
myCSharp.de-Poweruser/ Experte

avatar-2578.jpg


Dabei seit: 01.04.2008
Beiträge: 3.330
Entwicklungsumgebung: Visual Studio 2015/17


Th69 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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).
11.09.2019 13:16 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
T-Virus T-Virus ist männlich
myCSharp.de-Mitglied

Dabei seit: 17.04.2008
Beiträge: 1.284
Entwicklungsumgebung: Visual Studio, Codeblocks, Edi
Herkunft: Nordhausen, Nörten-Hardenberg


T-Virus ist offline Füge T-Virus Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

@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

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von T-Virus am 11.09.2019 13:54.

11.09.2019 13:53 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Charly Charly ist männlich
myCSharp.de-Mitglied

Dabei seit: 12.03.2014
Beiträge: 29

Themenstarter Thema begonnen von Charly

Charly ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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 ...
11.09.2019 14:16 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
gfoidl gfoidl ist männlich
myCSharp.de-Team

avatar-2894.jpg


Dabei seit: 07.06.2009
Beiträge: 6.562
Entwicklungsumgebung: VS 2019
Herkunft: Waidring


gfoidl ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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ü
11.09.2019 22:27 Beiträge des Benutzers | zu Buddylist hinzufügen
LaTino LaTino ist männlich
myCSharp.de-Poweruser/ Experte

avatar-4122.png


Dabei seit: 03.04.2006
Beiträge: 2.970
Entwicklungsumgebung: Rider / VS2019 / VS Code
Herkunft: Thüringen


LaTino ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ü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
12.09.2019 08:03 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
gfoidl gfoidl ist männlich
myCSharp.de-Team

avatar-2894.jpg


Dabei seit: 07.06.2009
Beiträge: 6.562
Entwicklungsumgebung: VS 2019
Herkunft: Waidring


gfoidl ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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 durch
  • geschicktere Algorithmen / geschicktere Verwendung von BCL-Typen

  • Verwendung vom  ArrayPool<T>, mit dem sich die Speicherallokationen amortisieren lassen
    Alternativ auch MemoryPool<T>, etc. Generell durch Poolen von Objekten die häufig verwendet werden

  • Stack-Allokationen mittels stackalloc -- vorzugsweise in Verbindung mit Span<T>, denn hier ist das Allozieren und Deallozieren vom Speichern nur eine Pointer-Operation (und das Nullen vom Allozierten Speicher), d.h. sehr schnell
mfG Gü
12.09.2019 13:34 Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum
Antwort erstellen


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