性能问题:与String.Format比较

Joe*_*orn 15 c# string performance

不久之后,Jon Skeet的一篇文章在我的脑海中建立了一个CompiledFormatter类,用于循环而不是使用String.Format().

这个想法是String.Format()花费解析格式字符串的调用部分是开销; 我们应该能够通过将代码移到循环之外来提高性能.当然,诀窍是新代码应该与行为完全匹配String.Format().

本周我终于做到了.我使用了Microsoft提供.Net框架源来直接调整他们的解析器(事实证明String.Format()实际上是将工作集中到了StringBuilder.AppendFormat()).我提出的代码是有效的,因为我的结果在我的(公认有限的)测试数据中是准确的.

不幸的是,我还有一个问题:表现.在我的初始测试中,我的代码的性能与正常情况的性能非常接近String.Format().根本没有改善; 它甚至一直慢几毫秒.至少它仍然处于相同的顺序(即:缓慢的数量不会增加;即使测试集增长,它仍然在几毫秒内),但我希望有更好的东西.

内部调用可能StringBuilder.Append()是实际推动性能的因素,但我想看看这里的聪明人是否可以帮助改进.

以下是相关部分:

private class FormatItem
{
    public int index; //index of item in the argument list. -1 means it's a literal from the original format string
    public char[] value; //literal data from original format string
    public string format; //simple format to use with supplied argument (ie: {0:X} for Hex

    // for fixed-width format (examples below) 
    public int width;    // {0,7} means it should be at least 7 characters   
    public bool justify; // {0,-7} would use opposite alignment
}

//this data is all populated by the constructor
private List<FormatItem> parts = new List<FormatItem>(); 
private int baseSize = 0;
private string format;
private IFormatProvider formatProvider = null;
private ICustomFormatter customFormatter = null;

// the code in here very closely matches the code in the String.Format/StringBuilder.AppendFormat methods.  
// Could it be faster?
public String Format(params Object[] args)
{
    if (format == null || args == null)
        throw new ArgumentNullException((format == null) ? "format" : "args");

    var sb = new StringBuilder(baseSize);
    foreach (FormatItem fi in parts)
    {
        if (fi.index < 0)
            sb.Append(fi.value);
        else
        {
            //if (fi.index >= args.Length) throw new FormatException(Environment.GetResourceString("Format_IndexOutOfRange"));
            if (fi.index >= args.Length) throw new FormatException("Format_IndexOutOfRange");

            object arg = args[fi.index];
            string s = null;
            if (customFormatter != null)
            {
                s = customFormatter.Format(fi.format, arg, formatProvider);
            }

            if (s == null)
            {
                if (arg is IFormattable)
                {
                    s = ((IFormattable)arg).ToString(fi.format, formatProvider);
                }
                else if (arg != null)
                {
                    s = arg.ToString();
                }
            }

            if (s == null) s = String.Empty;
            int pad = fi.width - s.Length;
            if (!fi.justify && pad > 0) sb.Append(' ', pad);
            sb.Append(s);
            if (fi.justify && pad > 0) sb.Append(' ', pad);
        }
    }
    return sb.ToString();
}

//alternate implementation (for comparative testing)
// my own test call String.Format() separately: I don't use this.  But it's useful to see
// how my format method fits.
public string OriginalFormat(params Object[] args)
{
    return String.Format(formatProvider, format, args);
}
Run Code Online (Sandbox Code Playgroud) 补充说明:

我担心为我的构造函数提供源代码,因为我不确定依赖于原始.Net实现的许可影响.但是,任何想要测试它的人都可以公开相关的私有数据并分配模仿特定格式字符串的值.

此外,如果有人提出可以改善构建时间的建议,我非常乐意改变FormatInfo课程甚至parts列表.由于我主要关心的是从前到后的连续迭代时间,可能LinkedList会更好吗?

[更新]:

嗯......我可以尝试的其他方法是调整我的测试.我的基准测试非常简单:将名称组合成一种"{lastname}, {firstname}"格式,并根据区号,前缀,数字和扩展组件组成格式化的电话号码.这些都没有对字符串中的字面段有太多影响.当我考虑原始状态机解析器是如何工作的时候,我认为那些文字段正是我的代码最好的机会,因为我不再需要检查字符串中的每个字符.

另一个想法:

这个类仍然有用,即使我不能让它变得更快.只要性能不差比基的String.Format(),我仍然创造了一个强类型的接口,它允许一个程序来组装它自己的"格式字符串"在运行时.我需要做的就是提供对零件清单的公共访问.

Joe*_*orn 8

这是最终结果:

我将基准测试中的格式字符串更改为应该更多地支持我的代码:

快速的棕色{0}跳过了懒惰的{1}.

正如我所料,与原版相比,这种情况要好得多; 此代码在5.3秒内完成200万次迭代,而6.1秒则为6.1秒String.Format.这是一个不可否认的改进.你可能甚至很想开始使用它作为许多String.Format情况下的简单替代品.毕竟,你不会做得更糟,你甚至可以获得一个小的性能提升:14%,这没什么可打喷嚏的.

除此之外.请记住,在专门设计用于支持此代码的情况下,我们仍然会在200 万次尝试中谈论不到半秒的差异.甚至没有繁忙的ASP.Net页面可能会产生那么大的负担,除非你很幸运能够在Top100网站上工作.

最重要的是,这省略了一个重要的选择:您可以StringBuilder每次创建一个新的并使用原始Append()调用手动处理您自己的格式.有了这种技术,我的基准测试只用了3.9秒. 这是一个更大的进步.

所以最后,如果你遇到性能问题的情况,那么有更好的选择.如果无关紧要,您可能希望坚持使用简单的内置方法.