字符串ctor是将IEnumerable <char>转换为字符串</char>的最快方法

时间:2013-02-08 18:17:07

标签: c# performance

编辑发布.Net Core 2.1

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

  

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

     

1000000次“new String”迭代耗时1009ms。

     

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

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


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


我正在考虑my answer to a previous question,我开始怀疑,是吗,

return new string(charSequence.ToArray());

IEnumerable<char>转换为string的最佳方法。我做了一点搜索,发现这个问题已经问here。那个答案断言,

string.Concat(charSequence)

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

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

return sb.ToString();

虽然这可能有点笨拙我把它包括在内以保证完整性。我决定做一点测试,使用的代码在底部。

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

  

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

     

1000000次“new String”迭代花了869ms。

     

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

据我估算,new string(...ToArray())接近string.Concat方法的两倍。 StringBuilder稍微快一些,但使用起来很尴尬,但可能是一个扩展。

我应该坚持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;
        }
    }
}

2 个答案:

答案 0 :(得分:6)

值得注意的是,这些结果虽然从纯粹主义者的观点来看属于IE的情况,但并不总是如此。例如,如果您实际上有一个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");
}

答案 1 :(得分:0)

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

  1. 使用StringBuilder并重复调用其Append(char ch)方法。
  2. 使用string.Concat<T>
  3. 使用String构造函数。
  4. 生成随机1,000个字符序列并从中构建字符串的10,000次迭代,我在发布版本中看到以下时间:

    • 形式= StringBuilder的 已用时间为00:01:05.9687330分钟。
    • 形式= StringConcatFunction 已用时间为00:02:33.2672485分钟。
    • 形式= StringConstructor 已用时间为00:04:00.5559091分钟。

    StringBuilder明显的赢家。不过,我正在使用静态StringBuilder(单例)实例。 Dunno,如果这有很大的不同。

    这是源代码:

    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 ;
        }
      }
    }