PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [.Net/C#] String-Operationen: Performance


Kabelsalat
2006-02-24, 22:07:30
Da die Verwendung des StringBuilders den zu verkettenden Text (im Quelltext) sehr unleserlich macht kam ich auf den Gedanken, evtl String.Format zu verwenden, allerdings musste ich mir Sorgen über deren Performance machen. Bei einer Google-Suche bin ich sodann über folgende Seite gestolpert: http://www.dotnetslackers.com/Performance/re-18455_String_Concatenation_StringBuilder_and_String_Format.aspx

OK, String.Format ist mit Abstand am langsamsten, aber damit konnte man rechnen - das Ergebnis des StringBuilders hat mich jedoch sehr verwundert... was haltet ihr von den Ergebnissen?

Trap
2006-02-24, 22:13:27
Ich halte das Testprogramm für sehr fragwürdig.

Xmas
2006-02-24, 22:23:52
Der zusammengesetze Text ist bei dem Test zu kurz dass StringBuilder da seine Vorteile ausspielen könnte. Und zu lang, dass die Default-Kapazität reichen würde. EnsureCapacity kann da Wunder wirken wenn man die Länge des Strings bereits vorher kennt.

Kabelsalat
2006-02-24, 22:30:16
Der zusammengesetze Text ist bei dem Test zu kurz dass StringBuilder da seine Vorteile ausspielen könnte. Und zu lang, dass die Default-Kapazität reichen würde. EnsureCapacity kann da Wunder wirken wenn man die Länge des Strings bereits vorher kennt.

Das leuchtet ein. Danke.

PS: Wird auch von folgender Seite bestätigt: http://dotnet.sys-con.com/read/46342.htm?CFID=365952&CFTOKEN=8DC4653D-669A-1487-E3F913A245EE28CD

Trap
2006-02-24, 22:44:26
Hab mal eben ein Testprogram in C# reingehackt:
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
class Program
{
String[] testdata;

String formatTest()
{
String x = "";
for (int i = 0; i < testdata.Length; i = i + 5)
{
x = String.Format("Result {0}, {1}, {2}, {3}, {4}", testdata[i], testdata[i + 1], testdata[i + 2], testdata[i + 3], testdata[i + 4]);
}
return x;
}

String concatTest()
{
String x = "";
for (int i = 0; i < testdata.Length; i = i + 5)
{
x = "Result " + testdata[i] + ", " + testdata[i + 1] + ", " + testdata[i + 2] + ", " + testdata[i + 3]+ ", " + testdata[i + 4];
}
return x;
}

String SBTest()
{
String x = "";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < testdata.Length; i = i + 5)
{
sb.Remove(0,sb.Length);
sb.Append("Result ");
sb.Append(testdata[i]);
sb.Append(", ");
sb.Append(testdata[i+1]);
sb.Append(", ");
sb.Append(testdata[i+2]);
sb.Append(", ");
sb.Append(testdata[i+3]);
sb.Append(", ");
sb.Append(testdata[i+4]);
x = sb.ToString();
}
return x;
}

String SBTestbetter()
{
String x = "";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < testdata.Length; i = i + 5)
{
sb.Remove(0, sb.Length);
sb.Append("Result ");
sb.Append(testdata[i]);
sb.Append(", ");
sb.Append(testdata[i + 1]);
sb.Append(", ");
sb.Append(testdata[i + 2]);
sb.Append(", ");
sb.Append(testdata[i + 3]);
sb.Append(", ");
sb.Append(testdata[i + 4]);
}
return sb.ToString();
}

void run()
{
testdata = new String[3000000];
for (int i = 0; i < testdata.Length; ++i)
{
testdata[i] = i.ToString();
}

DateTime now = DateTime.Now;
String outcome = formatTest();
TimeSpan dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = concatTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = SBTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = SBTestbetter();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = formatTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = concatTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = SBTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = SBTestbetter();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);
Console.ReadKey();
}

static void Main(string[] args)
{
new Program().run();
}
}
}

Ergebnisse:
Format 1,803
String+ 1,001
StringBuilder 0,931
StringBuilderbesser 0,611

Der StringBuilder ist nur lahm wenn man ihn falsch verwendet.

grakaman
2006-02-24, 23:45:43
Hab mal eben ein Testprogram in C# reingehackt:
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
class Program
{
String[] testdata;

String formatTest()
{
String x = "";
for (int i = 0; i < testdata.Length; i = i + 5)
{
x = String.Format("Result {0}, {1}, {2}, {3}, {4}", testdata[i], testdata[i + 1], testdata[i + 2], testdata[i + 3], testdata[i + 4]);
}
return x;
}

String concatTest()
{
String x = "";
for (int i = 0; i < testdata.Length; i = i + 5)
{
x = "Result " + testdata[i] + ", " + testdata[i + 1] + ", " + testdata[i + 2] + ", " + testdata[i + 3]+ ", " + testdata[i + 4];
}
return x;
}

String SBTest()
{
String x = "";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < testdata.Length; i = i + 5)
{
sb.Remove(0,sb.Length);
sb.Append("Result ");
sb.Append(testdata[i]);
sb.Append(", ");
sb.Append(testdata[i+1]);
sb.Append(", ");
sb.Append(testdata[i+2]);
sb.Append(", ");
sb.Append(testdata[i+3]);
sb.Append(", ");
sb.Append(testdata[i+4]);
x = sb.ToString();
}
return x;
}

String SBTestbetter()
{
String x = "";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < testdata.Length; i = i + 5)
{
sb.Remove(0, sb.Length);
sb.Append("Result ");
sb.Append(testdata[i]);
sb.Append(", ");
sb.Append(testdata[i + 1]);
sb.Append(", ");
sb.Append(testdata[i + 2]);
sb.Append(", ");
sb.Append(testdata[i + 3]);
sb.Append(", ");
sb.Append(testdata[i + 4]);
}
return sb.ToString();
}

void run()
{
testdata = new String[3000000];
for (int i = 0; i < testdata.Length; ++i)
{
testdata[i] = i.ToString();
}

DateTime now = DateTime.Now;
String outcome = formatTest();
TimeSpan dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = concatTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = SBTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = SBTestbetter();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = formatTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = concatTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = SBTest();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);

now = DateTime.Now;
outcome = SBTestbetter();
dtime = DateTime.Now - now;
Console.WriteLine("{0}\t{1:0.000}", outcome, ((double)(dtime.Ticks)) / TimeSpan.TicksPerSecond);
Console.ReadKey();
}

static void Main(string[] args)
{
new Program().run();
}
}
}

Ergebnisse:
Format 1,803
String+ 1,001
StringBuilder 0,931
StringBuilderbesser 0,611

Der StringBuilder ist nur lahm wenn man ihn falsch verwendet.

Ich würde jetzt sagen, dass das so nicht stimmt. Du erstellst ja vor der Schleife den SB. Klar ist es dann schneller, aber dafür sind alle Ergebnisse aller Schleifendurchgänge in einem SB Objekt zusammengewürfelt. Ich habe das jetzt so verstanden, dass ein Schleifendurchgang eine unabhängige Operation ist, von der man das Ergebnis möchte. Das möchte man ja dann auch in einem extra Objekt, deswegen sind es ja auch Subs und geben nichts zurück (habe ich zumindest so verstanden).

Trap
2006-02-25, 00:19:27
Das ist das Problem mit benchmarks die nichts sinnvolles machen: Man kann Zeug weglassen, es macht immernoch nichts sinnvolles und das schneller.

Der Code in SBTest erzeugt auf jeden Fall genauso viele unterschiedliche String-Objekte wie die beiden anderen Tests auch erzeugen, macht also exakt das gleiche.
SBTestbetter macht nicht genau das gleiche, das stimmt.

grakaman
2006-02-25, 00:31:20
Der Code in SBTest erzeugt auf jeden Fall genauso viele unterschiedliche String-Objekte wie die beiden anderen Tests auch erzeugen, macht also exakt das gleiche.


IMO nein, denn du lieferst ja in der Schleife nur ein String Objekt (mit sb.ToString()) aus dem SB zurück, dass ja auch nur die Größe von den im SB hinzugefügten String Objekten hat. Dem steht aber die initial höherere Speicherallokation aus dem Bsp. im Link gegenüber, die du in deinem Bsp. nicht berücksichtigt. Es ist schon so wie Xmas sagte, für diese paar String ist der SB mit Standard Kapazität zu groß und die Allokation ist hier aufwendiger.


SBTestbetter macht nicht genau das gleiche, das stimmt.

Ich bezog mich ja mit meiner Aussage auch auf die SBTestbetter, hatte ich vergessen zu erwähnen :(

Xmas
2006-02-25, 00:34:58
Es ist schon so wie Xmas sagte, für diese paar String ist der SB mit Standard Kapazität zu groß und die Allokation ist hier aufwendiger.
Andersrum, der SB mit Standardkapazität ist zu klein, und muss deswegen später nochmal vergrößert werden.

grakaman
2006-02-25, 00:38:42
Andersrum, der SB mit Standardkapazität ist zu klein, und muss deswegen später nochmal vergrößert werden.

Oh ja, also entsteht in dem Bsp. aus dem Link dann pro Durchgang zusätzliches Herumkopieren vom sb in einen größeren.

Trap
2006-02-25, 01:34:49
Dem steht aber die initial höherere Speicherallokation aus dem Bsp. im Link gegenüber, die du in deinem Bsp. nicht berücksichtigt.
Das Beispiel im Link ist ziemlich dumm geschrieben, es ist völlig unsinnig für jeden Schleifendurchlauf einen StringBuilder zu erzeugen. Es reicht aus in jedem Durchlauf einmal .ToString() aufzurufen.

grakaman
2006-02-25, 01:47:41
Das Beispiel im Link ist ziemlich dumm geschrieben, es ist völlig unsinnig für jeden Schleifendurchlauf einen StringBuilder zu erzeugen. Es reicht aus in jedem Durchlauf einmal .ToString() aufzurufen.

Das habe ich anfänglich auch gedacht, aber so ist es IMO nicht gemeint. Diese Schleifendurchgänge sind IMO (so habe ich es verstanden) doch nur da, um Last zu generieren bzw. brauchbare Messwerte zu bekommen. Die Operationen in einem Schleifendurchgang stehen in keinem Zusammenhang zu denen eines anderen. In einer wirklichen Anwendung würden solche Operationen wie in einem Schleifendurchgang überall in der Anwendung stattfinden (nur eben nicht zusammenhängend), was die Schleife simulieren soll. Da kannst du keinen globalen StringBuilder verwenden.

Trap
2006-02-25, 12:31:22
Eine Anwendung, bei der überall im Programm verteilte einzelne String-Concatanationen wichtig für die Performance sind, ist aber auch sehr künstlich.

StringBuilder benutzt man doch sowieso nur um performancekritische Codeteile zu optimieren und die sind eigentlich immer Schleifen für die eine feste Anzahl von StringBuilder-Objekt die vor der Schleife erzeugt werden ausreicht.

grakaman
2006-02-25, 14:23:14
Eine Anwendung, bei der überall im Programm verteilte einzelne String-Concatanationen wichtig für die Performance sind, ist aber auch sehr künstlich.

StringBuilder benutzt man doch sowieso nur um performancekritische Codeteile zu optimieren und die sind eigentlich immer Schleifen für die eine feste Anzahl von StringBuilder-Objekt die vor der Schleife erzeugt werden ausreicht.

Mmmhm, das finde ich nun überhaupt nicht künstlich, zumindest praxisnaher als performancekritische Teile (was immer das genau heißen mag). Vor allem im GUI Bereich kommt sowas zum Einsatz, z.B. um irgendwelche dynamischen Texte anzuzeigen. Wo es noch viel exzessiver verwendet wird, sind Webanwendungen, da man dort oft auf Serverseite HTML/Javascript zusammenbastelt.