11

针对 .Net Core 2.1 的发布进行了编辑

重复测试.Net Core 2.1的发布,我得到这样的结果

“Concat”的 1000000 次迭代耗时 842 毫秒。

“新字符串”的 1000000 次迭代耗时 1009 毫秒。

“sb”的 1000000 次迭代耗时 902 毫秒。

简而言之,如果您使用的是 .Net Core 2.1 或更高版本,Concat则为王。


我已经编辑了这个问题,以纳入评论中提出的有效观点。


我正在思考我对上一个问题的回答,我开始怀疑,这是,

return new string(charSequence.ToArray());

将 a转换IEnumerable<char>string. 我做了一点搜索,发现这个问题已经在这里问过了。该答案断言,

string.Concat(charSequence)

是更好的选择。在回答了这个问题之后,StringBuilder还建议了一种枚举方法,

var sb = new StringBuilder();
foreach (var c in chars)
{
    sb.Append(c);
}

return sb.ToString();

虽然这可能有点笨拙,但为了完整起见,我将其包括在内。我决定我应该做一个小测试,使用的代码在底部。

当在发布模式下进行优化,并在没有附加调试器的情况下从命令行运行时,我会得到这样的结果。

“Concat”的 1000000 次迭代耗时 1597 毫秒。

“新字符串”的 1000000 次迭代耗时 869 毫秒。

“sb”的 1000000 次迭代耗时 748 毫秒。

据我估计,该方法new string(...ToArray())的速度几乎是该方法的两倍string.ConcatStringBuilder仍然稍微快一点,但是使用起来很尴尬,但可以作为扩展。

我应该坚持new string(...ToArray())还是,我错过了什么?

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

class Program
{
    private static void Main()
    {
        const int iterations = 1000000;
        const string testData = "Some reasonably small test data";

        TestFunc(
            chars => new string(chars.ToArray()),
            TrueEnumerable(testData),
            10,
            "new String");

        TestFunc(
            string.Concat,
            TrueEnumerable(testData),
            10,
            "Concat");

        TestFunc(
            chars =>
            {
                var sb = new StringBuilder();
                foreach (var c in chars)
                {
                    sb.Append(c);
                }

                return sb.ToString();
            },
            TrueEnumerable(testData),
            10,
            "sb");

        Console.WriteLine("----------------------------------------");

        TestFunc(
            string.Concat,
            TrueEnumerable(testData),
            iterations,
            "Concat");

        TestFunc(
            chars => new string(chars.ToArray()),
            TrueEnumerable(testData),
            iterations,
            "new String");

        TestFunc(
            chars =>
            {
                var sb = new StringBuilder();
                foreach (var c in chars)
                {
                    sb.Append(c);
                }

                return sb.ToString();
            },
            TrueEnumerable(testData),
            iterations,
            "sb");

        Console.ReadKey();
    }

    private static TResult TestFunc<TData, TResult>(
            Func<TData, TResult> func,
            TData testData,
            int iterations,
            string stage)
    {
        var dummyResult = default(TResult);

        var stopwatch = Stopwatch.StartNew();
        for (var i = 0; i < iterations; i++)
        {
            dummyResult = func(testData);
        }

        stopwatch.Stop();
        Console.WriteLine(
            "{0} iterations of \"{2}\" took {1}ms.",
            iterations,
            stopwatch.ElapsedMilliseconds,
            stage);

        return dummyResult;
    }

    private static IEnumerable<T> TrueEnumerable<T>(IEnumerable<T> sequence)
    {
        foreach (var t in sequence)
        {
            yield return t;
        }
    }
}
4

2 回答 2

7

值得注意的是,这些结果虽然从纯粹主义者的角度来看对于 IEnumerable 的情况是正确的,但并不总是如此。例如,如果您实际上有一个 char 数组,即使您将其作为 IEnumerable 传递,调用字符串构造函数也会更快。

结果:

Sending String as IEnumerable<char> 
10000 iterations of "new string" took 157ms. 
10000 iterations of "sb inline" took 150ms. 
10000 iterations of "string.Concat" took 237ms.
======================================== 
Sending char[] as IEnumerable<char> 
10000 iterations of "new string" took 10ms.
10000 iterations of "sb inline" took 168ms.
10000 iterations of "string.Concat" took 273ms.

编码:

static void Main(string[] args)
{
    TestCreation(10000, 1000);
    Console.ReadLine();
}

private static void TestCreation(int iterations, int length)
{
    char[] chars = GetChars(length).ToArray();
    string str = new string(chars);
    Console.WriteLine("Sending String as IEnumerable<char>");
    TestCreateMethod(str, iterations);
    Console.WriteLine("===========================================================");
    Console.WriteLine("Sending char[] as IEnumerable<char>");
    TestCreateMethod(chars, iterations);
    Console.ReadKey();
}

private static void TestCreateMethod(IEnumerable<char> testData, int iterations)
{
    TestFunc(chars => new string(chars.ToArray()), testData, iterations, "new string");
    TestFunc(chars =>
    {
        var sb = new StringBuilder();
        foreach (var c in chars)
        {
            sb.Append(c);
        }
        return sb.ToString();
    }, testData, iterations, "sb inline");
    TestFunc(string.Concat, testData, iterations, "string.Concat");
}
于 2013-02-11T17:55:25.550 回答
1

好吧,我刚刚写了一个小测试,尝试了 3 种从 IEnumerable 创建字符串的不同方法:

  1. 使用StringBuilder并重复调用其Append(char ch)方法。
  2. 使用string.Concat<T>
  3. 使用String构造函数。

生成随机 1,000 个字符序列并从中构建字符串的 10,000 次迭代,我在发布版本中看到以下时间:

  • Style=StringBuilder 经过的时间是 00:01:05.9687330 分钟。
  • Style=StringConcatFunction 已用时间为 00:02:33.2672485 分钟。
  • Style=StringConstructor 经过的时间是 00:04:00.5559091 分钟。

StringBuilder明显的赢家。不过,我使用的是静态StringBuilder(单例)实例。不知道这是否有很大的不同。

这是源代码:

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

namespace ConsoleApplication6
{
  class Program
  {

    static readonly RandomNumberGenerator Random = RandomNumberGenerator.Create() ;

    static readonly byte[] buffer = {0,0} ;

    static char RandomChar()
    {
      ushort codepoint ;
      do
      {
        Random.GetBytes(buffer) ;
        codepoint = BitConverter.ToChar(buffer,0) ;
        codepoint &= 0x007F ; // restrict to Unicode C0 ;
      } while ( codepoint < 0x0020 ) ;
      return (char) codepoint ;
    }

    static IEnumerable<char> GetRandomChars( int count )
    {
      if ( count < 0 ) throw new ArgumentOutOfRangeException("count") ;

      while ( count-- >= 0 )
      {
        yield return RandomChar() ;
      }
    }

    enum Style
    {
      StringBuilder = 1 ,
      StringConcatFunction = 2 ,
      StringConstructor = 3 ,
    }

    static readonly StringBuilder sb = new StringBuilder() ;
    static string MakeString( Style style )
    {
      IEnumerable<char> chars = GetRandomChars(1000) ;
      string instance ;
      switch ( style )
      {
      case Style.StringConcatFunction :
        instance = String.Concat<char>( chars ) ;
        break ;
      case Style.StringBuilder : 
        foreach ( char ch in chars )
        {
          sb.Append(ch) ;
        }
        instance = sb.ToString() ;
        break ;
      case Style.StringConstructor :
        instance = new String( chars.ToArray() ) ;
        break ;
      default :
        throw new InvalidOperationException() ;
      }
      return instance ;
    }

    static void Main( string[] args )
    {
      Stopwatch stopwatch = new Stopwatch() ;

      foreach ( Style style in Enum.GetValues(typeof(Style)) )
      {
        stopwatch.Reset() ;
        stopwatch.Start() ;
        for ( int i = 0 ; i < 10000 ; ++i )
        {
          MakeString( Style.StringBuilder ) ;
        }
        stopwatch.Stop() ;
        Console.WriteLine( "Style={0}, elapsed time is {1}" ,
          style ,
          stopwatch.Elapsed
          ) ;
      }
      return ;
    }
  }
}
于 2013-02-08T19:32:36.583 回答