简体   繁体   中英

Why StringBuilder.Replace is slower than String.Replace

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

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.

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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM