我是否在破坏StringBuilder的效率?

时间:2010-08-13 19:40:46

标签: c# .net stringbuilder

我开始使用StringBuilder而不是直接连接,但似乎它缺少一个关键的方法。所以,我自己实现了它,作为扩展:

public void Append(this StringBuilder stringBuilder, params string[] args)
{
    foreach (string arg in args)
        stringBuilder.Append(arg);
}

这会导致以下混乱:

StringBuilder sb = new StringBuilder();
...
sb.Append(SettingNode);
sb.Append(KeyAttribute);
sb.Append(setting.Name);

进入这个:

sb.Append(SettingNode, KeyAttribute, setting.Name);

我可以使用sb.AppendFormat("{0}{1}{2}",...,但这似乎更不受欢迎,但仍然更难阅读。我的扩展是一个好方法,还是以某种方式破坏了StringBuilder的好处?我不是试图过早地优化任何东西,因为我的方法更多的是关于可读性而不是速度,但我也想知道我不是自己在脚下拍摄。

10 个答案:

答案 0 :(得分:69)

我认为你的扩展没有问题。如果它适合你,那一切都很好。

我自己更喜欢:

sb.Append(SettingNode)
  .Append(KeyAttribute)
  .Append(setting.Name);

答案 1 :(得分:32)

这样的问题总是可以用简单的测试用例来解答。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace SBTest
{
    class Program
    {
        private const int ITERATIONS = 1000000;

        private static void Main(string[] args)
        {
            Test1();
            Test2();
            Test3();
        }

        private static void Test1()
        {
            var sw = Stopwatch.StartNew();
            var sb = new StringBuilder();

            for (var i = 0; i < ITERATIONS; i++)
            {
                sb.Append("TEST" + i.ToString("00000"),
                          "TEST" + (i + 1).ToString("00000"),
                          "TEST" + (i + 2).ToString("00000"));
            }

            sw.Stop();
            Console.WriteLine("Testing Append() extension method...");
            Console.WriteLine("--------------------------------------------");
            Console.WriteLine("Test 1 iterations: {0:n0}", ITERATIONS);
            Console.WriteLine("Test 1 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
            Console.WriteLine("Test 1 output length: {0:n0}", sb.Length);
            Console.WriteLine("");
        }

        private static void Test2()
        {
            var sw = Stopwatch.StartNew();
            var sb = new StringBuilder();

            for (var i = 0; i < ITERATIONS; i++)
            {
                sb.Append("TEST" + i.ToString("00000"));
                sb.Append("TEST" + (i+1).ToString("00000"));
                sb.Append("TEST" + (i+2).ToString("00000"));
            }

            sw.Stop();    
            Console.WriteLine("Testing multiple calls to Append() built-in method...");
            Console.WriteLine("--------------------------------------------");
            Console.WriteLine("Test 2 iterations: {0:n0}", ITERATIONS);
            Console.WriteLine("Test 2 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
            Console.WriteLine("Test 2 output length: {0:n0}", sb.Length);
            Console.WriteLine("");
        }

        private static void Test3()
        {
            var sw = Stopwatch.StartNew();
            var sb = new StringBuilder();

            for (var i = 0; i < ITERATIONS; i++)
            {
                sb.AppendFormat("{0}{1}{2}",
                    "TEST" + i.ToString("00000"),
                    "TEST" + (i + 1).ToString("00000"),
                    "TEST" + (i + 2).ToString("00000"));
            }

            sw.Stop();
            Console.WriteLine("Testing AppendFormat() built-in method...");
            Console.WriteLine("--------------------------------------------");            
            Console.WriteLine("Test 3 iterations: {0:n0}", ITERATIONS);
            Console.WriteLine("Test 3 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
            Console.WriteLine("Test 3 output length: {0:n0}", sb.Length);
            Console.WriteLine("");
        }
    }

    public static class SBExtentions
    {
        public static void Append(this StringBuilder sb, params string[] args)
        {
            foreach (var arg in args)
                sb.Append(arg);
        }
    }
}

在我的电脑上,输出为:

Testing Append() extension method...
--------------------------------------------
Test 1 iterations: 1,000,000
Test 1 milliseconds: 1,080
Test 1 output length: 29,700,006

Testing multiple calls to Append() built-in method...
--------------------------------------------
Test 2 iterations: 1,000,000
Test 2 milliseconds: 1,001
Test 2 output length: 29,700,006

Testing AppendFormat() built-in method...
--------------------------------------------
Test 3 iterations: 1,000,000
Test 3 milliseconds: 1,124
Test 3 output length: 29,700,006

因此,你的扩展方法只比Append()方法稍慢,并且比AppendFormat()方法略快,但在所有3种情况下,差异完全是无关紧要的。因此,如果您的扩展方法增强了代码的可读性,请使用它!

答案 2 :(得分:9)

创建额外阵列需要一点点开销,但我怀疑它是多少。你应该测量

如果结果表明创建字符串数组的开销很大,可以通过多次重载来缓解它 - 一个用于两个参数,一个用于三个,一个用于四个等等...所以只有当你获得更多数量的参数(例如六或七)时才需要创建数组。重载将是这样的:

public void Append(this builder, string item1, string item2)
{
    builder.Append(item1);
    builder.Append(item2);
}

public void Append(this builder, string item1, string item2, string item3)
{
    builder.Append(item1);
    builder.Append(item2);
    builder.Append(item3);
}

public void Append(this builder, string item1, string item2,
                   string item3, string item4)
{
    builder.Append(item1);
    builder.Append(item2);
    builder.Append(item3);
    builder.Append(item4);
}

// etc

然后使用params进行最后一次重载,例如

public void Append(this builder, string item1, string item2,
                   string item3, string item4, params string[] otherItems)
{
    builder.Append(item1);
    builder.Append(item2);
    builder.Append(item3);
    builder.Append(item4);
    foreach (string item in otherItems)
    {
        builder.Append(item);
    }
}

我当然希望这些(或者只是你原来的扩展方法)比使用AppendFormat更快 - 毕竟需要解析格式字符串。

请注意,我没有让这些重载以伪递归方式相互调用 - 我怀疑它们会被内联,但如果它们不是设置新堆栈帧的开销等可能最终变得重要。 (我们假设如果我们到目前为止,数组的开销很大。)

答案 3 :(得分:3)

除了一点开销之外,我个人认为没有任何问题。绝对更具可读性。只要你通过合理数量的参数,我就不会发现问题。

答案 4 :(得分:2)

从清晰度的角度来看,您的扩展程序还可以。

如果你的项目从不超过5或6项,那么最好简单地使用.append(x).append(y).append(z)格式。

如果您处理数千个项目,StringBuilder本身只会为您带来性能提升。此外,每次调用方法时都会创建数组。

所以,如果你为了清晰起见,那就没关系。如果你是为了提高效率,那么你可能走错了路。

答案 5 :(得分:1)

我不会说你正在削弱它的效率,但是当一种更有效的方法可用时,你可能会做一些效率低下的事情。 AppendFormat就是我想你想要的。如果经常使用的{0} {1} {2}字符串太难看了,我倾向于将格式字符串放在上面的consts中,所以外观与扩展名大致相同。

sb.AppendFormat(SETTING_FORMAT, var1, var2, var3);

答案 6 :(得分:1)

我最近没有测试过,但在过去,StringBuilder实际上比普通字符串连接(“this”+“that”)慢,直到你得到大约7个连接。

如果这是循环中没有发生的字符串连接,您可能需要考虑是否应该使用StringBuilder。 (在循环中,我开始担心使用普通字符串连接进行分配,因为字符串是不可变的。)

答案 7 :(得分:1)

可能更快,因为对于许多追加,它最多只执行一次重新分配/复制步骤。

public void Append(this StringBuilder stringBuilder, params string[] args)
{
    int required = stringBuilder.Length;
    foreach (string arg in args)
        required += arg.Length;
    if (stringBuilder.Capacity < required)
        stringBuilder.Capacity = required;
    foreach (string arg in args)
        stringBuilder.Append(arg);
}

答案 8 :(得分:0)

最终归结为哪一个导致较少的字符串创建。我有一种感觉,扩展将导致使用字符串格式更高的字符串数。但表现可能不会那么不同。

答案 9 :(得分:0)

克里斯,

受到this Jon Skeet response的启发(第二个回答),我稍微改写了你的代码。基本上,我添加了TestRunner方法,该方法运行传入函数并报告已用时间,从而消除了一些冗余代码。不要沾沾自喜,而应该作为自己的编程练习。我希望它有用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace SBTest
{
  class Program
  {
    private static void Main(string[] args)
    {
      // JIT everything
      AppendTest(1);
      AppendFormatTest(1);

      int iterations = 1000000;

      // Run Tests
      TestRunner(AppendTest, iterations);
      TestRunner(AppendFormatTest, iterations);

      Console.ReadLine();
    }

    private static void TestRunner(Func<int, long> action, int iterations)
    {
      GC.Collect();

      var sw = Stopwatch.StartNew();
      long length = action(iterations);
      sw.Stop();

      Console.WriteLine("--------------------- {0} -----------------------", action.Method.Name);
      Console.WriteLine("iterations: {0:n0}", iterations);
      Console.WriteLine("milliseconds: {0:n0}", sw.ElapsedMilliseconds);
      Console.WriteLine("output length: {0:n0}", length);
      Console.WriteLine("");
    }

    private static long AppendTest(int iterations)
    {
      var sb = new StringBuilder();

      for (var i = 0; i < iterations; i++)
      {
        sb.Append("TEST" + i.ToString("00000"),
                  "TEST" + (i + 1).ToString("00000"),
                  "TEST" + (i + 2).ToString("00000"));
      }

      return sb.Length;
    }

    private static long AppendFormatTest(int iterations)
    {
      var sb = new StringBuilder();

      for (var i = 0; i < iterations; i++)
      {
        sb.AppendFormat("{0}{1}{2}",
            "TEST" + i.ToString("00000"),
            "TEST" + (i + 1).ToString("00000"),
            "TEST" + (i + 2).ToString("00000"));
      }

      return sb.Length;
    }
  }

  public static class SBExtentions
  {
    public static void Append(this StringBuilder sb, params string[] args)
    {
      foreach (var arg in args)
        sb.Append(arg);
    }
  }
}

这是输出:

--------------------- AppendTest -----------------------
iterations: 1,000,000
milliseconds: 1,274
output length: 29,700,006

--------------------- AppendFormatTest -----------------------
iterations: 1,000,000
milliseconds: 1,381
output length: 29,700,006