繁体   English   中英

为什么StringBuilder比字符串连接慢?

[英]Why is StringBuilder slower than string concatenation?

与+串联相比,为什么StringBuilder变慢? StringBuilder旨在避免额外的对象创建,但为什么它会惩罚性能呢?

    static void Main(string[] args)
    {
        int max = 1000000;
        for (int times = 0; times < 5; times++)
        {
            Console.WriteLine("\ntime: {0}", (times+1).ToString());
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < max; i++)
            {
                string msg = "Your total is ";
                msg += "$500 ";
                msg += DateTime.Now;
            }
            sw.Stop();
            Console.WriteLine("String +\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));

            sw = Stopwatch.StartNew();
            for (int j = 0; j < max; j++)
            {
                StringBuilder msg = new StringBuilder();
                msg.Append("Your total is ");
                msg.Append("$500 ");
                msg.Append(DateTime.Now);
            }
            sw.Stop();
            Console.WriteLine("StringBuilder\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
        }
        Console.Read();
    }

在此输入图像描述

编辑:按建议移出范围变量:

在此输入图像描述

更改以便不会一直实例化StringBuilder,而是.Clear()它:

time: 1
String +    :   3348ms
StringBuilder   :   3151ms

time: 2
String +    :   3346ms
StringBuilder   :   3050ms

等等

请注意 ,这仍然测试完全相同的功能,但尝试更智能地重用资源。

代码:(也访问http://ideone.com/YuaqY

using System;
using System.Text;
using System.Diagnostics;

public class Program
{
    static void Main(string[] args)
    {
        int max = 1000000;
        for (int times = 0; times < 5; times++)
        {
            {
                Console.WriteLine("\ntime: {0}", (times+1).ToString());
                Stopwatch sw = Stopwatch.StartNew();
                for (int i = 0; i < max; i++)
                {
                    string msg = "Your total is ";
                    msg += "$500 ";
                    msg += DateTime.Now;
                }
                sw.Stop();
                Console.WriteLine("String +\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
            }

            {
                Stopwatch sw = Stopwatch.StartNew();
                StringBuilder msg = new StringBuilder();
                for (int j = 0; j < max; j++)
                {
                    msg.Clear();
                    msg.Append("Your total is ");
                    msg.Append("$500 ");
                    msg.Append(DateTime.Now);
                }
                sw.Stop();
                Console.WriteLine("StringBuilder\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
            }
        }
        Console.Read();
    }
}

您正在每次迭代时创建一个StringBuilder的新实例,这会产生一些开销。 既然你没有将它用于它实际意图做的事情(即:构建大字符串,否则需要许多字符串连接操作),看到比串联更糟糕的性能并不奇怪。

StringBuilder更常见的比较/用法如下:

string msg = "";
for (int i = 0; i < max; i++)
{
    msg += "Your total is ";
    msg += "$500 ";
    msg += DateTime.Now;
}

StringBuilder msg_sb = new StringBuilder();
for (int j = 0; j < max; j++)
{
    msg_sb.Append("Your total is ");
    msg_sb.Append("$500 ");
    msg_sb.Append(DateTime.Now);
}

有了这个,你会发现StringBuilder和串联之间存在显着的性能差异。 而“重要”是指数量级 ,而不是您在示例中观察到的~10%的差异。

由于StringBuilder不需要构建大量的中间字符串而不会被丢弃,因此可以获得更好的性能。 这就是它的意思。 对于较小的字符串,最好使用字符串连接以简化和清晰。

使用更长的字符串时,StringBuilder的好处应该是显而易见的。

每次连接字符串时都会创建一个新的字符串对象,因此字符串越长,从旧字符串复制到新字符串所需的越多。

此外,创建许多临时对象可能会对StopWatch无法测量的性能产生负面影响,因为它会使用临时对象“污染”托管堆,并可能导致更多垃圾回收周期。

修改你的测试以创建(更多)更长的字符串并使用(更多)更多的连接/追加操作,并且StringBuilder应该表现更好。

除了不以最有效的方式使用StringBuilder之外,您还没有尽可能有效地使用字符串连接。 如果你知道你提前连接了多少个字符串,那么在一行上完成这一切应该是最快的。 编译器优化操作,以便不生成任何中间字符串。

我添加了几个测试用例。 一个与sehe建议的基本相同,另一个在一行中生成字符串:

sw = Stopwatch.StartNew();
builder = new StringBuilder();
for (int j = 0; j < max; j++)
{
    builder.Clear();
    builder.Append("Your total is ");
    builder.Append("$500 ");
    builder.Append(DateTime.Now);
}
sw.Stop();
Console.WriteLine("StringBuilder (clearing)\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));

sw = Stopwatch.StartNew();
for (int i = 0; i < max; i++)
{
    msg = "Your total is " + "$500" + DateTime.Now;
}
sw.Stop();
Console.WriteLine("String + (one line)\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));

以下是我在机器上看到的输出示例:

time: 1
String +    :   3707ms
StringBuilder   :   3910ms
StringBuilder (clearing)    :   3683ms
String + (one line) :   3645ms

time: 2
String +    :   3703ms
StringBuilder   :   3926ms
StringBuilder (clearing)    :   3666ms
String + (one line) :   3625ms

一般来说: - 如果你在很多步骤中构建一个大字符串,或者你不知道将多少个字符串连接在一起, StringBuilder会做得更好。
- 只要它是一个合理的选项选项,将它们全部混合在一个表达式中就更好了。

注意

string msg = "Your total is ";
msg += "$500 ";
msg += DateTime.Now;

汇编到

string msg = String.Concat("Your total is ", "$500 ");
msg = String.Concat(msg, DateTime.Now.ToString());

每次迭代总计两个concats和一个ToString。 此外,单个String.Concat非常快,因为它知道结果字符串的大小,因此它只分配结果字符串一次,然后快速将源字符串复制到其中。 这意味着在实践中

String.Concat(x, y);

永远都会超越

StringBuilder builder = new StringBuilder();
builder.Append(x);
builder.Append(y);

因为StringBuilder不能使用这样的快捷方式(你可以调用一个附加的,或者一个删除,这是String.Concat无法实现的)。

StringBuilder的工作方式是分配一个初始缓冲区并将字符串长度设置为0.对于每个Append,它必须检查缓冲区,可能分配更多缓冲区空间(通常将旧缓冲区复制到新缓冲区),复制字符串并增加构建器的字符串长度。 String.Concat不需要做所有这些额外的工作。

因此,对于简单的字符串连接,x + y(即String.Concat)将始终优于StringBuilder。

现在,一旦开始将大量字符串连接到单个缓冲区中,或者你在缓冲区上进行大量操作,你就会开始从StringBuilder中获益,在不使用StringBuilder时你需要继续创建新的字符串。 。 这是因为StringBuilder只是偶尔以块的形式分配新的内存,但String.Concat,String.SubString等(几乎)总是分配新的内存。 (像“”.SubString(0,0)或String.Concat(“”,“”)之类的东西不会分配内存,但这些都是退化的情况。)

我认为比较String和StringBuilder之间的效率而不是时间更好。

msdn说:String被称为不可变的,因为它的值一旦创建就无法修改。 看似修改String的方法实际上返回一个包含修改的新String。 如果需要修改类似字符串的对象的实际内容,请使用System.Text.StringBuilder类。

string msg = "Your total is "; // a new string object
msg += "$500 "; // a new string object
msg += DateTime.Now; // a new string object

看哪一个更好。

下面是一个示例,演示了StringBuilder执行速度比字符串连接更快的情况:

static void Main(string[] args)
{
    const int sLen = 30, Loops = 10000;
    DateTime sTime, eTime;
    int i;
    string sSource = new String('X', sLen);
    string sDest = "";
    // 
    // Time StringBuilder.
    // 
    for (int times = 0; times < 5; times++)
    {
        sTime = DateTime.Now;
        System.Text.StringBuilder sb = new System.Text.StringBuilder((int)(sLen * Loops * 1.1));
        Console.WriteLine("Result # " + (times + 1).ToString());
        for (i = 0; i < Loops; i++)
        {
            sb.Append(sSource);
        }
        sDest = sb.ToString();
        eTime = DateTime.Now;
        Console.WriteLine("String Builder took :" + (eTime - sTime).TotalSeconds + " seconds.");
        // 
        // Time string concatenation.
        // 
        sTime = DateTime.Now;
        for (i = 0; i < Loops; i++)
        {
            sDest += sSource;
            //Console.WriteLine(i);
        }
        eTime = DateTime.Now;
        Console.WriteLine("Concatenation took : " + (eTime - sTime).TotalSeconds + " seconds.");
        Console.WriteLine("\n");
    }
    // 
    // Make the console window stay open
    // so that you can see the results when running from the IDE.
    // 
}

结果#1 String Builder占用:0秒。 连接采取:8.7659616秒。

结果#2 String Builder占用:0秒。 连接采取:8.7659616秒。

结果#3 String Builder占用:0秒。 连接采取:8.9378432秒。

结果#4 String Builder占用:0秒。 连接采取:8.7972128秒。

结果#5 String Builder占用:0秒。 连接采取:8.8753408秒。

StringBulder比+串联快得多..

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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