C# Performance Char[] -> String umwandeln

Ghost_Rider_R

Lieutenant
Registriert
Nov. 2009
Beiträge
759
Hallo zusammen,

ich habe festgestellt, dass es zeitlich extrem lange dauert ein Char[] in einen String umzuwandeln.
Gibt es hier eventuell eine performantere Möglichkeit?

Ich zähle z.B. in einer Schleife so schnell ich kann und sobald diese Zeile dazu kommt bricht die Performance massiv ein:

Char[] cmeinCharArray = new Char[] { 'c', 'r', 'a', 'p' };
String einString = new String(meinCharArray);


Vorher 3 Milliarden Hochzählungen pro Sekunde und nach diesen beiden Zeilen nur noch knapp über 100.000.000.

Es handelt sich hierbei um lokale Variablen, also jeder Thread hat seine eigenen Variablen.

Danke für die Info 😃

LG Ghost Rider
 
Was macht denn der Rest der Schleife?
 
  • Gefällt mir
Reaktionen: tollertyp
Ein new string erzeugt immer eine Kopie von deinem char[] array.
Was du machen kannst, ist folgendes, mal abgesehen von Algorithmus Änderungen:
1. Dein Char[] wiederverwenden
2. Auf .NET Core/.NET 5 string.Create nutzen, https://docs.microsoft.com/en-us/dotnet/api/system.string.create?view=net-5.0, wenn du weißt wie lange dein String werden wird, das spart dir dann dein char[]
3. Wenn du vorher Operationen auf den Char[] machst, ggf. dir Span/ReadonlySpan anschauen (primär auf .NET Core/.NET 5)

Ich persönlich gehe aber davon aus, dass dein Algorithmus einfach nicht wirklich optimiert ist.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Hayda Ministral und Dalek
Zu strings wurde ja schon einiges hier gesagt, aber kann es sein das deine Schleife ansonsten fast gar nichts macht? Vor allem nichts das Allokationen verursacht? Dann ist es nicht so unerwartet das das ganze deutlich langsamer wird sobald etwas mehr passiert. Irgeneinen Integer inkrementieren ist sehr schnell, die Garbage Collection auf Hochturen laufen zu lassen ist langsam.

Wenn ich mich nicht irgendwo verzählt habe dann braucht ein Durchlauf deiner Schleife mit String etwa 10 Nanosekunden, das ist immer noch recht schnell. Das ist aber auch eine Größenordnung wo man wirklich Allokationen vermeiden muss wenn man noch mehr rausholen will.
 
  • Gefällt mir
Reaktionen: Ebrithil und Arc Angeling
Anbei mal ein einfaches Beispiel, damit es jeder nachvollziehen kann was ich meine:

Einfaches Beispiel.jpg


In diesem Beispiel läuft die Berechnung nur auf einem Kern. Ich habe es zur Veranschaulichung jetzt stark vereinfacht. Die Leistung reduziert sich von über 720 Mio. auf ca. 93Mio. Das finde ich dann doch etwas heftig...

Bei meinem Projekt nutze ich alle Kerne, aber sobald diese Zeile kommt habe ich nur noch ca. 15% der Leistung ohne diese Zeile.

Kann man das eventuell irgendwie anders schreiben bzw. umgehen? Verwendet wird .NET 4.7.
Mein Projekt selber macht in der Methode schon mehr, aber eben wesentlich langsamer, sobald ein Char[] in einen String umgewandelt wird. Ich denke das Beispiel illustriert mein Problem ganz gut.

Anbei noch der Sourcecode (.txt) zum Download, falls jemand etwas probieren möchte:
https://we.tl/t-f5yz9VPLSe

Vielen Dank für eure Hilfe und liebe Grüße
Ghost Rider 🙈🙉🙊
 
In C würde die Zeile ungefähr so aussehen

Code:
size_t len = sizeof(meineChars) ;
char *str = malloc(len+1) ;
memcpy (str, meineChars , len) ;
str[len] = 0;
...
free (str) ;

Stimmt nicht ganz aber zeigt das eben relativ viel passiert.

Wenn du die allokation aus der Schleife heraus bringst wird der Code deutlich schneller.
 
  • Gefällt mir
Reaktionen: Ice-Lord
Ghost_Rider_R schrieb:
Die Leistung reduziert sich von über 720 Mio. auf ca. 93Mio. Das finde ich dann doch etwas heftig...
Dein Beispiel ist ziemlich nutzlos, du hast ne heisse Schleife die nichts macht und dann kommt plötzlich String dazu, das ist genau der Fall den dalek beschrieben hat.

Ohne deinen echten Quelltext zu kennen kann man dir detailliert nicht weiterhelfen.
 
  • Gefällt mir
Reaktionen: Ebrithil, Arc Angeling, cloudman und eine weitere Person
Wenn die Performance an der String-Performance scheitert, warum dann Strings verwenden? Die sind notorisch langsam, da ist nix neues.

Vielleicht einfach mal den Code für einen Thread hier posten und mehr oder weniger beschreiben was die Anwendung tun soll.

In den einzelnen Thread dann nur das absolut notwendigste stecken in der Form, daß der Thread das richtige Ergebnis liefert und man NICHTS entfernen kann, ohne daß sich dies ändert.

Ich geh derzeit einfach mal davon aus, daß die Erstellung von Strings im Thread nicht notwendig ist.

Arrays sind übrigens auch nicht performant, dafür gibts Listen. Evtl wären StringBuilder eine Option, wenns gar nicht anders geht, aber rein vom Ansatz her, wenn wirklich irgendwelche Strings "berechnet" werden sollen, dann würd ich erstmal List<char> von den Threads zurückgeben lassen, das Ergebnis im Anschluß aufbereiten und dann gucken.

Aber wenn es derzeit so aussieht, das jeder Thread sowas wie "Das ist der [n]te Thread" auf die Konsole schreibt, dann kann man da keine besondere Performance erwarten, sondern kommt besser weg wenn man den Thread das n zurückgeben läßt und die Ausgabe nachlagert.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
RalphS schrieb:
Arrays sind übrigens auch nicht performant, dafür gibts Listen.
Das halte ich mal für ein Gerücht, die Standard Liste im .NET Framework ist nur ein wrapper über ein Array und die aktuelle Position und Länge wird getracked damit das unterliegende Array entsprechend vergrößert (verdoppelt) wird, wenn es voll ist.
 
  • Gefällt mir
Reaktionen: Arc Angeling, Ice-Lord und KitKat::new()
RalphS schrieb:
Arrays sind übrigens auch nicht performant, dafür gibts Listen.
Zu dieser undifferenzierten und damit falschen Aussage gibt es eine Menge Leute, die sich wirklich damit beschäftigt haben, wie zum Beispiel bei Stackoverflow.
 
  • Gefällt mir
Reaktionen: KitKat::new()
Hallo zusammen,

@RalphS: danke, ungefähr so eine Antwort habe ich gesucht. Wenn Strings dafür bekannt sind, dann passt das für mich, dann habe ich inhaltlich keinen Fehler gemacht. Ich bin jetzt stattdessen bei den CHAR Arrays geblieben und habe damit eine wesentlich höhere Performance.

Vielen Dank an alle für eure Beiträge! 👍🏻
 
Es sind aber nicht die Strings die hier die Performance kosten sondern, dass du Speicher allokierst.
Wenn du dein char array in der Schleife erzeugst ist die performance noch schlechter

Code:
using System;
using System.Threading;

namespace crap
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.CursorVisible = false;
            DateTime start = DateTime.Now;
            long zaehler = 0;
            long etappenZaehler = 0;

            new Thread(() =>
            {
                while (true)
                {
                    Console.SetCursorPosition(0, 0);
                    Console.WriteLine("Zähler:\t\t" + zaehler.ToString("n0"));
                    Console.WriteLine("Loops / Sek:\t" + (zaehler / DateTime.Now.Subtract(start).TotalSeconds).ToString("n0"));
                    Thread.Sleep(300);
                }
            }).Start();

            string einString;
            while (true)
            {
                etappenZaehler++;
                if (etappenZaehler > 1000000)
                {
                    zaehler += etappenZaehler;
                    etappenZaehler = 0;
                }
                char[] meinCharArray = new char[] { 'c', 'r', 'a', 'p' };
            }
        }
    }
}
 
Warum weiß ich nicht aber ich habe es ausprobiert. Vermutlich weil die String Klasse schon optimiert ist wenn sie mit einem char array initialisiert wird.
Ich wollte nur zeigen, dass nicht die String Klasse das Problem ist sondern die Speicheranforderung
 
Ghost_Rider_R schrieb:
@RalphS: danke, ungefähr so eine Antwort habe ich gesucht. Wenn Strings dafür bekannt sind, dann passt das für mich, dann habe ich inhaltlich keinen Fehler gemacht.
Haarsträubend. Da postet jemand totalen Unsinn und glücklicherweise passt das dann zu den völlig falschen Schlussfolgerungen, die du aus deinem Code ziehst.

C#:
while (true)
{
    etappenZaehler++;
    if(etappenZaehler > 1000000)
    {
        zaehler += etappenZaehler;
        etappenZaehler = 0;
    }
    einString = new string(meinCharArray);
}

Die Schleife tut nichts sinnvolles außer Speicher für Strings zu alloziieren. Und natürlich wird das langsamer, als wenn man es nicht tut. Alles andere wäre ein ausgewachsenes Wunder.
Schon weiter oben im Code machst du definitiv inhaltliche Fehler:
C#:
Console.WriteLine("Zähler:\t\t" + zaehler.ToString("n0"));
Das führt zuerst dazu, das erst ein String für den Zähler angelegt wird. Anschließend wird ein zweiter String angelegt, als Verknüpfung der Einzel-Strings. Console.WriteLine hat dafür Überladungen, die diese doppelte Speicherbelegung vermeiden.
Ich empfehle die Ausführung von Microsoft zum Thema Strings. Sonst liest das hier vielleicht noch jemand, der auch nicht viel Ahnung von der Materie hat und macht das nach.

PS: <Ironie>Assembler ist noch schneller als dein char-Array. Unbedingt verwenden!</Ironie>
 
  • Gefällt mir
Reaktionen: Slvr und breedmaster
Ghost_Rider_R schrieb:
danke, ungefähr so eine Antwort habe ich gesucht.
Das erinnerst mich extrem an:
 
  • Gefällt mir
Reaktionen: Slvr und efcoyote
...meine Frage war ja letztlich:

Gibt es hier eventuell eine performantere Möglichkeit?

Diese denke ich kann für mein Vorhaben mit Nein beantwortet werden, da zwangslläufig immer Speicher für den String reserviert werden muss. Auch wenn die Klasse selbst dann performant ist ändert das nichts daran, dass dies wesentlich langsamer ist, als wenn ich einfach mit dem Char Array Arbeite. Wenn jemand etwas anderes weiß, dann nur zu.
 
Deine Frage ist so nicht zu beantworten
Du vergleichst - zumindest in dem Code den hier präsentierst- nichts tun mit neuem String erzeugen - klar ist das langsamer .
Wir haben schon ein paar mal gefragt was du eigentlich in der inneren Schleife machst
 
  • Gefällt mir
Reaktionen: Arc Angeling und KitKat::new()
Im Prinzip wollte ich einfach diesen String für weitere Anweisungen verwenden z.B. in eine Textdatei schreiben, auf der Konsole verwenden, Zeichenketten manipulieren oder vergleichen etc. Das war eher allgemeiner Natur. Wenn der String bereits erstellt ist geht es auch schnell, nur sobald dieser erst aus einem Char Array umgewandelt wird dauert es eben ewig. Das war mein Problem, mehr nicht.
 
Zurück
Oben