String.Format与StringBuilder一样高效

lom*_*axx 157 c# vb.net performance stringbuilder string.format

假设我在C#中有一个stringbuilder来执行此操作:

StringBuilder sb = new StringBuilder();
string cat = "cat";
sb.Append("the ").Append(cat).(" in the hat");
string s = sb.ToString();
Run Code Online (Sandbox Code Playgroud)

那将是有效的或更高效的:

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);
Run Code Online (Sandbox Code Playgroud)

如果是这样,为什么?

编辑

在一些有趣的答案之后,我意识到我可能应该对我的要求更加清楚.我没有那么多要求连接字符串哪个更快,但是一个字符串注入另一个字符串更快.

在上面两种情况下,我想将一个或多个字符串注入预定义模板字符串的中间.

对困惑感到抱歉

Kev*_*Kev 144

String.FormatStringBuilder内部使用:

public static string Format(IFormatProvider provider, string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }

    StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
    builder.AppendFormat(provider, format, args);
    return builder.ToString();
}
Run Code Online (Sandbox Code Playgroud)

上面的代码是mscorlib的片段,所以问题变成" StringBuilder.Append()StringBuilder.AppendFormat()" 更快"?

如果没有基准测试,我可能会说上面的代码示例会更快地运行.Append().但这是一个猜测,尝试基准测试和/或分析两者以获得适当的比较.

这个小伙伴Jerry Dixon做了一些基准测试:

http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

更新:

可悲的是,上面的链接已经死了.但是在Way Back Machine上还有一个副本:

http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

在一天结束时,它取决于你的字符串格式是否会被重复调用,即你正在对超过100兆字节的文本进行一些严肃的文本处理,或者当用户一次又一次地点击按钮时是否正在调用它.除非你正在做一些巨大的批处理工作,否则我会坚持使用String.Format,它有助于代码的可读性.如果您怀疑存在性能瓶颈,那么在您的代码上粘贴一个探查器,看看它到底在哪里.

  • Jerry Dixon页面基准测试的一个问题是他从不在`StringBuilder`对象上调用`.ToString()`.在很多次迭代中,那个时间产生了很大的不同,并且意味着他并不是在比较苹果和苹果.这就是他为"StringBuilder"展示如此出色表现的原因,并可能是他的惊喜.我只是重复了基准来纠正这个错误并得到了预期的结果:`String`` +`运算符最快,接着是`StringBuilder`,`String.Format`抬起后面. (7认同)
  • 6年后,现在已经不是这样了.在Net4中,string.Format()创建并缓存它重用的StringBuilder实例,因此在某些测试用例中它可能比StringBuilder更快.我在下面给出了一个修改后的基准测试(仍然说concat是最快的,对于我的测试用例,格式比StringBuilder慢10%). (5认同)

Gre*_*reg 45

MSDN文档:

String或StringBuilder对象的串联操作的性能取决于内存分配发生的频率.字符串连接操作始终分配内存,而StringBuilder连接操作仅在StringBuilder对象缓冲区太小而无法容纳新数据时分配内存.因此,如果连接固定数量的String对象,则String类更适合并置操作.在这种情况下,编译器甚至可以将单个连接操作组合成单个操作.如果连接任意数量的字符串,则StringBuilder对象最好用于连接操作; 例如,如果循环连接随机数量的用户输入字符串.


Vai*_*hav 12

我运行了一些快速性能基准测试,对于平均超过10次运行的100,000次操作,第一种方法(String Builder)几乎占用了第二种方法(字符串格式).

所以,如果这种情况很少发生,那也没关系.但如果这是一个常见的操作,那么您可能想要使用第一种方法.


McD*_*ell 10

我希望String.Format更慢 - 它必须解析字符串然后连接它.

几个笔记:

  • 格式是专业应用程序中用户可见字符串的方式; 这可以避免本地化错误
  • 如果事先知道结果字符串的长度,请使用StringBuilder(Int32)构造函数预定义容量


saa*_*lon 8

我认为在大多数情况下,这种清晰度,而不是效率,应该是您最关心的问题.除非你将大量的琴弦压碎,或者为低功率的移动设备制造一些东西,否则这可能不会对你的跑步速度产生很大的影响.

我发现,在我以相当线性的方式构建字符串的情况下,进行直接连接或使用StringBuilder是最佳选择.在你正在构建的大多数字符串是动态的情况下,我建议这样做.由于文本很少是静态的,因此最重要的是,每个动态文本的放置位置都很清楚,以防将来需要更新.

另一方面,如果你在谈论一大块带有两个或三个变量的静态文本,即使效率稍低,我认为你从string.Format中获得的清晰度值得.本周早些时候我不得不在4页文档的中心放置一点动态文本.如果将一大块文本更新为一组,而不是更新连接在一起的三个部分,则更容易更新.


Chr*_*oll 8

如果只是因为string.Format并没有完全按照你的想法行事,那么这是6年后在Net45上重新运行的测试.

Concat仍然是最快但实际上差异不到30%.StringBuilder和Format的差异仅为5-10%.我有20%的变化运行测试几次.

毫秒,一百万次迭代:

  • 连接:367
  • 每个键的新stringBuilder:452
  • Cached StringBuilder:419
  • string.Format:475

我带走的教训是,性能差异是微不足道的,所以它不应该阻止你编写最简单的可读代码.这对我的钱来说往往是但并非总是如此a + b + c.

const int iterations=1000000;
var keyprefix= this.GetType().FullName;
var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations);
Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength);

var concatkeys= new string[iterations];
var stringbuilderkeys= new string[iterations];
var cachedsbkeys= new string[iterations];
var formatkeys= new string[iterations];

var stopwatch= new System.Diagnostics.Stopwatch();
Console.WriteLine("Concatenation:");
stopwatch.Start();

for(int i=0; i<iterations; i++){
    var key1= keyprefix+":" + i.ToString();
    concatkeys[i]=key1;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("New stringBuilder for each key:");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString();
    stringbuilderkeys[i]= key2;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("Cached StringBuilder:");
var cachedSB= new StringBuilder(maxkeylength);
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString();
    cachedsbkeys[i]= key2b;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("string.Format");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key3= string.Format("{0}:{1}", keyprefix,i.ToString());
    formatkeys[i]= key3;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-');
Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);
Run Code Online (Sandbox Code Playgroud)

  • 通过"string.Format并不完全按照你的想法行事"我的意思是在4.5源代码中它试图创建并重用一个缓存的StringBuilder实例.所以我在测试中包含了这种方法 (2认同)

jri*_*sta 6

String.Format在StringBuilder内部使用...因此逻辑上导致了由于更多开销而导致性能稍差的想法.但是,简单的字符串连接是在另外两个字符串之间注入一个字符串的最快方法...在很大程度上.几年前Rico Mariani在他的第一次表演测验中证明了这一证据.简单的事实是连接......当字符串部分的数量已知时(没有限制......你可以连接一千个部分......只要你知道它总是1000个部分)...总是比StringBuilder字符串更快.格式.它们可以通过单个内存分配和一系列内存副本来执行.是证明

这里是一些String.Concat方法的实际代码,最终调用FillStringChecked,它使用指针复制内存(通过Reflector提取):

public static string Concat(params string[] values)
{
    int totalLength = 0;

    if (values == null)
    {
        throw new ArgumentNullException("values");
    }

    string[] strArray = new string[values.Length];

    for (int i = 0; i < values.Length; i++)
    {
        string str = values[i];
        strArray[i] = (str == null) ? Empty : str;
        totalLength += strArray[i].Length;

        if (totalLength < 0)
        {
            throw new OutOfMemoryException();
        }
    }

    return ConcatArray(strArray, totalLength);
}

public static string Concat(string str0, string str1, string str2, string str3)
{
    if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null)))
    {
        return Empty;
    }

    if (str0 == null)
    {
        str0 = Empty;
    }

    if (str1 == null)
    {
        str1 = Empty;
    }

    if (str2 == null)
    {
        str2 = Empty;
    }

    if (str3 == null)
    {
        str3 = Empty;
    }

    int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length;
    string dest = FastAllocateString(length);
    FillStringChecked(dest, 0, str0);
    FillStringChecked(dest, str0.Length, str1);
    FillStringChecked(dest, str0.Length + str1.Length, str2);
    FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3);
    return dest;
}

private static string ConcatArray(string[] values, int totalLength)
{
    string dest = FastAllocateString(totalLength);
    int destPos = 0;

    for (int i = 0; i < values.Length; i++)
    {
        FillStringChecked(dest, destPos, values[i]);
        destPos += values[i].Length;
    }

    return dest;
}

private static unsafe void FillStringChecked(string dest, int destPos, string src)
{
    int length = src.Length;

    if (length > (dest.Length - destPos))
    {
        throw new IndexOutOfRangeException();
    }

    fixed (char* chRef = &dest.m_firstChar)
    {
        fixed (char* chRef2 = &src.m_firstChar)
        {
            wstrcpy(chRef + destPos, chRef2, length);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

那么:

string what = "cat";
string inthehat = "The " + what + " in the hat!";
Run Code Online (Sandbox Code Playgroud)

请享用!