-1

According to the following unit test methods, StringBuilder is far slower than String.Replace, how come every one saying StringBuilder is faster? Am I missing something?

[TestMethod]
public void StringReplace()
{
    DateTime date = DateTime.Now;
    string template = File.ReadAllText("file.txt");
    for (int i = 0; i < 100000; i++)
    {
        template = template.Replace("cat", "book" );
        template = template.Replace("book", "cat"); 
    }
    Assert.Fail((DateTime.Now - date).Milliseconds.ToString()); 
}

[TestMethod]
public void StringBuilder()
{
    DateTime date = DateTime.Now;
    StringBuilder template = new StringBuilder(File.ReadAllText("file.txt"));
    for (int i = 0; i < 100000; i++)
    {
        template.Replace("cat", "book");
        template.Replace("book", "cat"); 
    }
    Assert.Fail((DateTime.Now - date).Milliseconds.ToString());
}

Here is the result:

StringReplace - 335ms

StringBuilder - 799ms

Reinderien
  • 11,755
  • 5
  • 49
  • 77
sino
  • 754
  • 1
  • 7
  • 22
  • 1
    might help to show your output – Jonesopolis Aug 20 '13 at 18:41
  • 1
    If that's your actual code, you are not testing on the same file. – driis Aug 20 '13 at 18:42
  • 3
    Also, don't time things using DateTime. There's a `Stopwatch` class that's good for this sort of thing. *And* (for completeness sake) you might want to rephrase your question to sound less argumentative. – Chris Pfohl Aug 20 '13 at 18:43
  • 3
    That benchmark isn't even correct. You're testing loading the file plus replacing the string when you really just want to test the latter. – Dustin Kingen Aug 20 '13 at 18:43
  • 7
    This is not the use case where StringBuilder is faster - you are just creating new strings of exactly the same length, instead of slowly growing one by appending more pieces. – Alexei Levenkov Aug 20 '13 at 18:44
  • just updated the code , I renamed file for privacy reason , but yes it is same file also I added the results – sino Aug 20 '13 at 18:44
  • `template = template.Replace("cat", "book" ); template = template.Replace("book", "cat");` is not a valid way to swap `cat` and `book`. It will end up just replacing both with `cat`. And not just whole words, but e.g. "bookish" will become "catish", and "cookbook" will be "cookcat". – Tim S. Aug 20 '13 at 18:46
  • the first value in text file is cat so the replacing will start from there . – sino Aug 20 '13 at 18:50
  • I tried many combinations of replacing and always String.Replace wins – sino Aug 20 '13 at 18:51
  • @ Alexei Levenkov: what is the suggested replace scenario that you think StringBulder will do better ? – sino Aug 20 '13 at 18:53
  • 1
    Also, `TimeSpan.Milliseconds` is not the way to see the time taken, you need `.TotalMilliseconds` – gregmac Aug 20 '13 at 18:54
  • @gregmac : I just changed it to TotalMilliseconds and StringBuilder still slower : SB:4700 ms String.Replace: 1700ms – sino Aug 20 '13 at 18:57
  • @sino, no one is saying `StringBuilder` is faster at replacing strings. Also, your benchmarks are poorly written. –  Aug 20 '13 at 19:13
  • @Amy: take a look here :http://stackoverflow.com/questions/6524528/string-replace-vs-stringbuilder-replace – sino Aug 20 '13 at 19:16

4 Answers4

8

StringBuilder is faster at building strings. Replacing is a different concern.

Eg, the following example code:

    [TestMethod]
    public void StringConcat()
    {
        var start = DateTime.Now;

        var s = string.Empty;
        for (int i = 0; i < 100000; i++)
        {
            s += "cat";
        }
        Assert.Fail((DateTime.Now - start).TotalMilliseconds.ToString()); 
    }

    [TestMethod]
    public void StringBuilder()
    {
        var start = DateTime.Now;

        var sb = new StringBuilder();
        for (int i = 0; i < 100000; i++)
        { 
            sb.Append("cat");
        }
        Assert.Fail((DateTime.Now - start).TotalMilliseconds.ToString()); 
    }

For me, I get 14,645ms (14.6 seconds) and 2ms respectively.

gregmac
  • 24,276
  • 10
  • 87
  • 118
  • So stringBuilder is not string heavent as some show it , String.Replace will do better when it is pure replacing process – sino Aug 20 '13 at 19:14
  • 1
    But how often do you really concatenate 100 000 small strings together? [Jeff Atwood has something to say about string concatenation](http://www.codinghorror.com/blog/2009/01/the-sad-tragedy-of-micro-optimization-theater.html) – Bart van Nierop Aug 20 '13 at 19:28
  • 100 000 the number is experimental – sino Aug 20 '13 at 19:38
3

According to several tests (links to more tests at the bottom) as well as a quick and sloppy test of my own, String.Replace performs better than StringBuilder.Replace. You do not seem to be missing anything.

For completeness sake, here's my testing code:

int big = 500;
String s;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; ++i)
{
    sb.Append("cat mouse");
}
s = sb.ToString();

Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < big; ++i)
{ 
    s = s.Replace("cat", "moo"); 
    s = s.Replace("moo", "cat"); 
}
sw.Stop(); Trace.WriteLine(sw.ElapsedMilliseconds); sw.Reset(); sw.Start();
for (int i = 0; i < big; ++i)
{
    sb.Replace("cat", "moo");
    sb.Replace("moo", "cat");
}
sw.Stop(); Trace.WriteLine(sw.ElapsedMilliseconds); sw.Reset(); sw.Start();
for (int i = 0; i < big; ++i)
{
    s = s.Replace("cat", "mooo");
    s = s.Replace("mooo", "cat");
}
sw.Stop(); Trace.WriteLine(sw.ElapsedMilliseconds); sw.Reset(); sw.Start();
for (int i = 0; i < big; ++i)
{
    sb.Replace("cat", "mooo");
    sb.Replace("mooo", "cat");
}
sw.Stop(); Trace.WriteLine(sw.ElapsedMilliseconds);

The output, on my machine, is:

9
11
7
1977

[EDIT]

I missed one very important case. That is the case where every time the string is replaced with something else. This could matter because of the way C# handles strings. What follows is the code that tests the missing case, and the results on my system.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
class Program
{
    static void Main()
    {
        var repl = GenerateRandomStrings(4, 500);
        String s;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 100; ++i)
        {
            sb.Append("cat mouse");
        }
        s = sb.ToString();
        Stopwatch sw = new Stopwatch();
        sw.Start();
        foreach (string str in repl)
        {
            s = s.Replace("cat", str);
            s = s.Replace(str, "cat");
        }
        sw.Stop(); Trace.WriteLine(sw.ElapsedMilliseconds); sw.Reset(); sw.Start();
        foreach (string str in repl)
        {
            sb.Replace("cat", str);
            sb.Replace(str, "cat");
        }
        sw.Stop(); Trace.WriteLine(sw.ElapsedMilliseconds);
    }

    static HashSet<string> GenerateRandomStrings(int length, int amount)
    {
        HashSet<string> strings = new HashSet<string>();
        while (strings.Count < amount)
            strings.Add(RandomString(length));           
        return strings;
    }

    static Random rnd = new Random();
    static string RandomString(int length)
    {
        StringBuilder b = new StringBuilder();
        for (int i = 0; i < length; ++i)
            b.Append(Convert.ToChar(rnd.Next(97, 122)));
        return b.ToString();
    }
}

Output:

8
1933

However, as we start to increase the length of the random strings, the StringBuilder solution comes closer and closer to the String solution. For random strings with a length of 1000 characters, my results are

138
328

Using this new knowledge on the old tests, I get similar results when increasing the length of the string to replace with. When replacing with a string that is a thousand 'a' characters instead of "mooo", my results for the original answer become:

8
11
160
326

Although the results do become closer, it still seems that for any real world use, String.Replace beats StringBuilder.Replace.

Bart van Nierop
  • 4,130
  • 2
  • 28
  • 32
  • yeah that is what seem to be , hope other developers notice that too – sino Aug 20 '13 at 19:40
  • I missed the case where the string we're replacing with is not always the same. I edited the answer accordingly. Also, it turns out that when replacements become longer, StringBuilder comes closer to String. I added that to the answer as well. – Bart van Nierop Aug 20 '13 at 20:36
  • Is there any way to check which one consume more memory ? – sino Aug 21 '13 at 18:04
  • A quick and free way to do that would be to use [CLR Profiler](http://www.microsoft.com/en-us/download/details.aspx?id=16273). There are also [questions](http://stackoverflow.com/questions/2629034/how-do-i-profile-memory-usage-in-my-project) right here on Stack Overflow. – Bart van Nierop Aug 21 '13 at 18:37
  • @BartvanNierop, What .net version did you test? The internal implementation changed between 2 and 4. In .net 2 there is a single string buffer which means replace could be around the same speed. In .net 4 there are multiple string arrays which means replacing across arrays would be very slow compared to replacing in a single string. – Will Aug 22 '13 at 10:25
  • @Will, this was tested in .net 4.0. I reran the scenarios in .net 2.0 and then `StringBuilder` appears to be marginally faster on my machine. – Bart van Nierop Sep 01 '13 at 13:44
  • @BartvanNierop, I just tested your code on my machine and StringBuilder.Replace is the same speed as String.Replace for .net 2.0. Are you sure you are timing in release mode without the debugger attached? Also how much memory does your machine have free(Your machine might be garbage collecting)? Also are you sure after changing to 2.0 your binary was actually rebuilt? – Will Sep 01 '13 at 15:50
0

StringBuilder is fastern to concatenate strings:

"abc" + "cde" implies creating a new string "abccde" copying all information "abc" and "cde" to the new string

With stringbuilder you concatenate "abc" with "cde" just adding the text, so there is no need to create a new class and copy all information

The replace function need a string to work, so each iteration of stringbuilder have to create the string, replace, and regenerate the stringbuilder class

user149113
  • 46
  • 4
  • 1
    SB is faster *where the number of strings being concatted isn't known at compile time*. `"abc" + "cde"` will result in concatenation *at compile time* and thus will be faster than using a SB. – Servy Aug 20 '13 at 18:57
  • But all the C# developers community agree that StringBuilder is faster for replacing string.you just need to google to see how many articles about that. I have now real project and doesnt seem to see it really doing better than String.Replace – sino Aug 20 '13 at 19:00
  • 2
    @sino `But all the C# developers community agree ` – Servy Aug 20 '13 at 19:03
  • "abc" + "cde" is an example, agree with it's linked in compiled time, I just want to expose a + b where a is a string and b is a string. And string is fastern than stringbuilder doing replaces: http://blogs.msdn.com/b/debuggingtoolbox/archive/2008/04/02/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance.aspx – user149113 Aug 20 '13 at 19:08
  • @user149113 But SB *isn't* faster whenever your concatting strings, that's my point. It's only when the number of strings being concatenated isn't known at compile time. `string1 + string2 + string3 + string4` is faster than using a string builder. `foreach(var s in strings)aggregateString += s;` is what you want to avoid. *That's* where you use a SB. – Servy Aug 20 '13 at 19:09
  • @Servy, yes, As I said that I'm agree, SB is fastern to append strings when the strings are not known in compilation time. I put my example just to clarify but is too much simple and the compiler can do better with simple strings. – user149113 Aug 20 '13 at 19:13
  • 1
    "But all the C# developers community agree that StringBuilder is faster for replacing string." I've never heard such a claim. This hardly constitutes "all the C# developers". –  Aug 20 '13 at 19:15
-3

StringBuilder is faster for 2+ strings. When you only need to concatenate 2 strings, actually doing + is faster.

Alex
  • 2,342
  • 1
  • 18
  • 30
  • `+` is only defined for two strings. You can't concatenate three or more strings with `+`. –  Aug 20 '13 at 19:26
  • you are saying you can't do "asdf" + "foo" + "boo"? – Alex Aug 20 '13 at 23:31
  • That is two uses of `+`. Each `+` is a function with two parameters. It is the same as writing `("asdf" + "foo") + "boo"` –  Aug 21 '13 at 15:22
  • ok, so i am still correct and ppl who downvoted me did not look at assembly for doing StringBuilder vs. String concatenation using +. If they did, they would know that i am right. please do your homework, ppl – Alex Aug 21 '13 at 20:12
  • 1
    You're being downvoted because your "answer" doesn't address the actual question, which involves string replacement. And as I said, `+` concatenation doesn't even exist for three or more strings, so it isn't faster. Do you understand that `+` concatenation allocates a new string every time? In your example, five strings will be allocated. `+` might be fast for short strings, but long ones will take longer to allocate, and every allocation means a future garbage collection. –  Aug 21 '13 at 22:42
  • what are you talking about? i am correct and my answer is correct, please re-read the question. – Alex Aug 22 '13 at 14:16
  • Okay. Please show me in the question where the OP is asking about string concatenation. Then stop trolling. Repeatedly asserting that you're correct doesn't make it so, and is only serving to make you look immature. –  Aug 22 '13 at 15:21
  • oh, you are right :) i read last answer and thought the question was about concatenating strings, sorry about that. still, on concatenating strings using + vs String Builder, i am still correct. – Alex Aug 22 '13 at 17:27
  • Whatever you say, chief. –  Aug 22 '13 at 18:09