Laden...

Stringbuilder Replace - Wird der Wert newValue auch abgerufen wenn kein Platzhalter gefunden wird?

Erstellt von Stryder vor 13 Jahren Letzter Beitrag vor 13 Jahren 4.125 Views
S
Stryder Themenstarter:in
26 Beiträge seit 2008
vor 13 Jahren
Stringbuilder Replace - Wird der Wert newValue auch abgerufen wenn kein Platzhalter gefunden wird?

Nach Recherche im Internet habe ich herausgefunden, dass Stringbuilder Replace performanter ist als String Replace wenn man wie ich viele Platzhalter ersetzen möchte, da beim Stringbuilder am eigentlichen String gearbeitet wird und nicht jeweils ein neuer erstellt wird der dann zugewiesen wird.

Meine Frage ist allerdings:
Wenn ich sehr viele solcher Befehle habe:

StringBuilder b = new StringBuilder(text);

b.Replace("%Artist%", SDK_getValue("CurrentMedia.Artist"));
b.Replace("%Title%", SDK_getValue("CurrentMedia.Title"));
...

Wird

SDK_getValue("CurrentMedia.Artist")

auch abgerufen wenn %Artist% nicht gefunden wurde?

Ich möchte erreichen, dass die neuen Werte auch wirklich nur dann abgerufen werden wenn der Text vorkommt. (Damit nicht unnötig viele Abfragen gemacht werden)
Reicht es also so, oder muss ich zusätzlich vorher noch ein Contains aufrufen?

916 Beiträge seit 2008
vor 13 Jahren

Hallo Stryder,

nein es reicht so natürlich nicht. Den du rufst ja die Methode Replace auf und da muss der zu ersetzende Wert der Methode übergeben werden. Wenn du vorher checken willst, müsstest du z.b. via Contains() schauen ob der Wert drin ist, und dann Replcae aufrufen.

Was nun jedoch performanter ist, deine SDK Methode oder die Contains das musst du selber heraus finden.

Again what learned...

S
Stryder Themenstarter:in
26 Beiträge seit 2008
vor 13 Jahren

Ok, ich danke dir.

Das heißt: Da Stringbuilder kein Contains besitzt muss ich auf jeden Fall schon mit einer Kombination arbeiten...

Wäre ja auch zu schön gewesen.

//Edit: Eine Idee kam mir noch in den Sinn: Was meint ihr, wäre es vielleicht performanter wenn ich mit einem geeigneten Regex Befehl alle Platzhalter aufliste und dann nur diese abarbeite? Ich bin mir nicht sicher ob das schneller wäre als einzelne Contains für jeden Platzhalter.

916 Beiträge seit 2008
vor 13 Jahren

Am StringBuilder gibt es, wie an jeden anderen Object auch, die ToString() Methode an der du dann wiederrum das Contains aufrufen kannst. Aber wie gesagt, du solltest prüfen ob das am Ende dann performater ist!

Again what learned...

S
Stryder Themenstarter:in
26 Beiträge seit 2008
vor 13 Jahren

Also ich habe grade mal einen Test der drei Varianten gemacht:

Mit einem relativ komischen Ergebnis

Variante 1: Stringbuilder
Hier werden alle Abfragen an die SDK weitergeleitet, ob der Text nun vorkommt oder nicht.

Ergebnis: Hier dauert es am längsten. Bei einer Standardabfrage. Allerdings: Umso länger das Programm läuft und die Werte gleich bleiben umso kürzer die Abfrage. (Annäherend 0)

>> Zu Beginn: 5,85568051407115E-05 , Später: 0,000104154317340446

Variante 2: Stringbuilder und String.Contains
Immer gleiche Dauer der Ausführung. Manchmal unwesentlich schneller als String.Contains & String.Replace aber fast durchgehend auf gleichem Level

>> 3,16782716334997E-05 || 4,4157590761848E-05

Variante 3: String.Replace und String.Contains
Die Ausgangsvariante. Diese ist durchschnittlich immernoch die schnellste, allerdings gibt es hier nicht diesen eigenartigen Performance Effekt, bei dem plötzlich die Dauer auf annähernd 0 herabsinkt wenn das Programm länger läuft und die Werte gleich bleiben.

>> 3,26382192587572E-05 || 2,83184549450982E-05

Gelöschter Account
vor 13 Jahren

@rollerfreak2: die Idee war Sinnfrei. ToString() erzeugt einen neuen String aus dem Stringbuilder.... also wozu dann noch den Stringbuilder wenn man vor jeder Operation ohnehin einen komplett neuen String erzeugt und dort dann Contains macht?

@Stryder:
Wie hast du gemessen?

Ich halte die Regex-Stringbuilder Variante für die schnellste.
Zuerst mit Regex alle Platzhalter holen und nur diese dann im Stringbuilder ersetzen.
Dann entfällt auch das ständige Contains.
Wichtig ist nur, das du beim Regex gleiche Treffer filterst. Also das du nicht ein und den selben Platzhalter aus versehend 2 mal oder mehrfach setzt.

916 Beiträge seit 2008
vor 13 Jahren

Hallo JAck30lena,

@rollerfreak2: die Idee war Sinnfrei. ToString() erzeugt einen neuen String aus dem Stringbuilder.... also wozu dann noch den Stringbuilder wenn man vor jeder Operation ohnehin einen komplett neuen String erzeugt und dort dann Contains macht?

Das ist richtig, aber mehrfach muss man das ToString() nicht aufrufen, einmal reicht ja.

Ich finde allerdings die Regex Variante sowieso am "schönsten", und sie wird m.E. auch die schnellste sein.

@Stryder
Mit was du gemessen hast würde mich auch interessieren!

Again what learned...

S
Stryder Themenstarter:in
26 Beiträge seit 2008
vor 13 Jahren

Mit dieser Klasse hier, die nehme ich meistens für sowas:

internal class HiPerfTimer
    {
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(
            out long lpPerformanceCount);

        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(
            out long lpFrequency);

        private long startTime, stopTime;
        private long freq;

        // Constructor

        public HiPerfTimer()
        {
            startTime = 0;
            stopTime = 0;

            if (QueryPerformanceFrequency(out freq) == false)
            {
                // high-performance counter not supported

                throw new Win32Exception();
            }
        }

        // Start the timer

        public void Start()
        {
            // lets do the waiting threads there work

            Thread.Sleep(0);

            QueryPerformanceCounter(out startTime);
        }

        // Stop the timer

        public void Stop()
        {
            QueryPerformanceCounter(out stopTime);
        }

        // Returns the duration of the timer (in seconds)

        public double Duration
        {
            get
            {
                return (double)(stopTime - startTime) / (double)freq;
            }
        }
    }

Also ihr schlagt vor: Erst Regex um die Platzhalter zu finden und dann mit switch oder wie würdet ihr das machen?

Gelöschter Account
vor 13 Jahren

Ich habe gefragt, wie du gemessen hast. Nicht mit was.

Erst Regex um die Platzhalter zu finden und dann mit switch oder wie würdet ihr das machen?

Mit regex herausfinden, welche Platzhalter exisiteren und dann mit einer Foreach iterieren und ersetzen ... also möglichst generisch. Je nach abweichung würde ich eine Mappingtabelle oder Dictionary bezüglich Platzhalterstring und GetValue-Token erzeugen.

S
Stryder Themenstarter:in
26 Beiträge seit 2008
vor 13 Jahren

Ok, das wäre dann die Messung hier:
Das habe ich dann eine weile im normalen Ablauf laufen lassen und mir dann die Log Datei angeschaut.



HiPerfTimer pt = new HiPerfTimer();

pt.Start();
StringBuilder a = new StringBuilder(text);

a.Replace("{Artist}", SDK_getValue("CurrentMedia.Artist"));
a.Replace("{Title}", SDK_getValue("CurrentMedia.Title"));
a.Replace("{Album}", SDK_getValue("CurrentMedia.Album"));
a.Replace("{Position}", SDK_getValue("CurrentMedia.Position"));
a.Replace("{Duration}", SDK_getValue("CurrentMedia.Duration"));
a.Replace("{Mode}", SDK_getValue("CurrentMedia.Mode"));

string wurst = a.ToString();
pt.Stop();
WriteLog("Stringbuilder: " + pt.Duration.ToString());

pt.Start();
StringBuilder c = new StringBuilder(text);
if (text.Contains("{Artist}"))
 c.Replace("{Artist}", SDK_getValue("CurrentMedia.Artist"));
                                                                
if (text.Contains("{Title}"))
c.Replace("{Title}", SDK_getValue("CurrentMedia.Title"));
                                                                
if (text.Contains("{Album}"))
c.Replace("{Album}", SDK_getValue("CurrentMedia.Album"));
                                                                
if (text.Contains("{Position}"))
c.Replace("{Position}", SDK_getValue("CurrentMedia.Position"));
                                                                
if (text.Contains("{Duration}"))
c.Replace("{Duration}", SDK_getValue("CurrentMedia.Duration"));
                                                                
if (text.Contains("{Mode}"))
c.Replace("{Mode}", SDK_getValue("CurrentMedia.Mode"));

string wurst2 = c.ToString();
pt.Stop();
WriteLog("Stringbuilder-Contains: " + pt.Duration.ToString());

pt.Start();
if (text.Contains("{Artist}"))
text = text.Replace("{Artist}", SDK_getValue("CurrentMedia.Artist"));

if (text.Contains("{Title}"))
text = text.Replace("{Title}", SDK_getValue("CurrentMedia.Title"));

if (text.Contains("{Album}"))
 text = text.Replace("{Album}", SDK_getValue("CurrentMedia.Album"));

                                                             
if (text.Contains("{Position}"))
 text = text.Replace("{Position}", SDK_getValue("CurrentMedia.Position"));

if (text.Contains("{Duration}"))
text = text.Replace("{Duration}", SDK_getValue("CurrentMedia.Duration"));

if (text.Contains("{Mode}"))
 text = text.Replace("{Mode}", SDK_getValue("CurrentMedia.Mode"));

pt.Stop();
 WriteLog("Contains und Replace: " + pt.Duration.ToString());

Also beim Dictionary hab ich doch auch wieder das Problem, dass ich jeden Wert erst von dem SDK abrufen muss um sie einzuspeichern?

Mappingtabelle hab ich ehrlich gesagt noch nie benutzt, das müsste ich mir dann mal ansehen.

Das ist wohl eher unperformant, was?

foreach (string placeholder in placeholders)
{
    if (placeholder = "{Artist}")
       ...
    else if (placeholder = "{Title}")
     ...
}
1.361 Beiträge seit 2007
vor 13 Jahren

@zeitmessung:

Die klasse System.Diagnostics.Stopwatch nutzt intern den highperformancecounter. Es gibt also keinen Grund hier selbst die winapi zu nutzen.

Beste grüße
Zommi

Gelöschter Account
vor 13 Jahren

Also beim Dictionary hab ich doch auch wieder das Problem, dass ich jeden Wert erst von dem SDK abrufen muss um sie einzuspeichern?

Nein. Es geht um das Mapping von "{Artist}" zu "CurrentMedia.Artist"

Dann kann deine Foreach so aussehen:

foreach (string placeholder in placeholders)
{
     myStringBuilder.Replace(placeholder, SDK_getValue(mappingDictionary[placeholder]);
}
4.938 Beiträge seit 2008
vor 13 Jahren

Hallo Stryder,

Zu Beginn: 5,85568051407115E-05 , Später: 0,000104154317340446

Du weißt schon was 'E-05" bedeutet, oder?

Und einmalige Zeitmessungen sind völlig irrelevant. Mach Iterationen von einigen 10.000 bis 1Mio. (so daß die Gesamtdauer Unterschiede im Sekundenbereich ergibt)!

S
Stryder Themenstarter:in
26 Beiträge seit 2008
vor 13 Jahren

Oh das hab ich ganz übersehen.

Das heißt das ist: 0,0000585568051407115 richtig?

Dann ist die Methode also auf jeden fall schon deutlich schneller... 👍

Gelöschter Account
vor 13 Jahren

Aber nicht so schnell wie die anderen Versionen...

S
Stryder Themenstarter:in
26 Beiträge seit 2008
vor 13 Jahren

Ja das stimmt, die sind ja auch alle um 5 verschoben.

Ich werde jetzt mal deinen Vorschlag umsetzten. Davon erhoffe ich mir am meisten.
Muss mich nur mal jetzt mit dem Regex auseinandersetzen, damit ich das hinbekomme das er alle Wörter mit geschweiften Klammern herausfiltert.

//Edit: Der Vollständigkeit halber hier das Regex Pattern "{.*}"

//Edit2: So habe jetzt eine eigene Klasse geschrieben die die Zeit genauer misst, wie Th69 vorgeschlagen hat.

Durchgänge habe ich 100.000 gewählt.

Variante1: Regex + Dictionary + Stringbuilder.Replace
=> Deutlich am langsamsten: 12,6994 Sekunden

Variante2: String.Contains + Stringbuilder.Replace
=> 2,2458 Sekunden

Variante3: String.Contains + String.Replace
=> 1,74401 Sekunden

Das einzige was ich hierbei noch nicht testen konnte waren die Abfragen mit dem SDK. Ich habe jetzt nur Dummy Werte zum ersetzen genommen.

//Edit3: Ich habe nun noch eine weitere Möglichkeit getestet, nämlich
String.Contains + String.Replace + Dictionary.

Das gruppiert sich wie erwartet, zwischen Variante 2 und 3 und ist damit zwar nicht die allerschnellste Variante, dafür aber die vom Code her sauberste.

1.378 Beiträge seit 2006
vor 13 Jahren

Meiner Meinung nach total überflüssige Tests.

Du wolltest wissen welche Variante die schnellste ist in Kombination mit den SDK Abfragen und da wette ich ebenfalls auf Regex. Probier mal folgende Variante:


        private readonly Regex _regex = new Regex(@"\{[^\}]+\}", RegexOptions.Compiled);
        private string ReplaceTags(string input)
        {
            return _regex.Replace(input, match => SDK_getValue("CurrentMedia." + match.Value));
        }

du brauchst diesen Aufruf nur einmal für alle deine Tags durchführen(vorausgesetzt alle Tags schauen so aus {....}) und nicht wie bei deinen anderen Varianten mit einer Schleife oder einem haufen If-Statements.

Lg XXX

Gelöschter Account
vor 13 Jahren

Diese Methode würde ich nicht verwenden, da du für jeden einzelnen Platzhalter den Aufruf durchführst (also wenn 2 mal der selbe platzhalter vorkommt wird auch 2 mal der aufruf durchgeführt) und weil du abhängig von der bezieung vom Platzhalter und Aufrufname bist.

S
Stryder Themenstarter:in
26 Beiträge seit 2008
vor 13 Jahren

Das "Problem ist" eigentlich das der User mit einer xml Datei das Layout der Ausgabe bestimmt.

Das heißt: Er kann dort Positionen eingeben wo dann die einzelnen Werte (die er selber aussuchen kann) erscheinen sollen.

So sieht das dann aus

<Content type="Text" position="1;58" datasource="MEDIA" style="Text2">{Artist}</Content>

Was ich dabei nicht bedacht habe, ist, dass fast ausschließlich immer genau ein Tag dort steht, sodass in meinen Augen die Regex Variante wenig sinn macht.
Dazu kommt, dass ich alle möglichen Strings auch im Vorhinein kenne, sodass auch dort der Vorteil der Regex Variante flach fällt.

Wovon ich mir jetzt aber einen größeren Performanceschub erhoffe ist folgendes: Ich abonniere die nächstes / vorheriges Lied Events und rufe dann einmal aus der SDK die Werte Titel, Interpret usw. ab.
Erst beim Songwechsel beginnt das Spiel von neuem.

Dann brauch ich schonmal nur die Sachen wir Spieldauer, Uhrzeit usw. jedes Mal abrufen.

1.378 Beiträge seit 2006
vor 13 Jahren

Man kann diese Version ja gewiss noch ausbauen.

Hier eine Variante bei der mehrere Vorkommnisse gecached werden.


        private readonly Regex _regex = new Regex(@"\{[^\}]+\}", RegexOptions.Compiled);
        private string ReplaceTags(string input)
        {
            var replaceCache = new Dictionary<string, string>();
            return _regex.Replace(input, match =>
            {
                string key = match.Value;

                if (!replaceCache.ContainsKey(key))
                    replaceCache.Add(key, SDK_getValue("CurrentMedia." + key));

                return replaceCache[key];
            });
        }

Auch die Abhängigkeit lässt sich natürlich durch ein Dictionary auflösen. Warum aber den Aufwand, wenns unter Umständen nicht gefordert ist.