性能问题:与String.Format进行比较 [英] Performance issue: comparing to String.Format

查看:102
本文介绍了性能问题:与String.Format进行比较的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

前一段时间,乔恩·斯凯特(Jon Skeet)的帖子在我脑海中构建了一个 CompiledFormatter 类的想法,以便在循环中使用,而不是在 String中使用.Format()

A while back a post by Jon Skeet planted the idea in my head of building a CompiledFormatter class, for using in a loop instead of String.Format().

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

The idea is the portion of a call to String.Format() spent parsing the format string is overhead; we should be able to improve performance by moving that code outside of the loop. The trick, of course, is the new code should exactly match the String.Format() behavior.

这周我终于做到了。我通过使用来直接修改其解析器(事实证明, String.Format()实际上将工作场code> StringBuilder.AppendFormat())。我提出的代码可以正常工作,因为在(有限的)测试数据中我的结果是准确的。

This week I finally did it. I went through using the .Net framework source provided by Microsoft to do a direct adaption of their parser (it turns out String.Format() actually farms the work to StringBuilder.AppendFormat()). The code I came up with works, in that my results are accurate within my (admittedly limited) test data.

不幸的是,我仍然有一个问题:性能。在我的初始测试中,我的代码的性能与普通 String.Format()的性能非常匹配。根本没有改善;甚至持续慢了几毫秒。至少它仍然是相同的顺序(即:减慢的数量没有增加;即使随着测试集的增长,它也保持在几毫秒之内),但是我希望有更好的东西。

Unfortunately, I still have one problem: performance. In my initial tests the performance of my code closely matches that of the normal String.Format(). There's no improvement at all; it's even consistently a few milliseconds slower. At least it's still in the same order (ie: the amount slower doesn't increase; it stays within a few milliseconds even as the test set grows), but I was hoping for something better.

内部调用 StringBuilder.Append()可能实际上是提高性能的原因,但我想看看这里的聪明人可以帮助改善事物。

It's possible that the internal calls to StringBuilder.Append() are what actually drive the performance, but I'd like to see if the smart people here can help improve things.

以下是相关部分:

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



附加说明:

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

Additional notes:

I'm wary of providing the source code for my constructor, because I'm not sure of the licensing implications from my reliance on the original .Net implementation. However, anyone who wants to test this can just make the relevant private data public and assign values that mimic a particular format string.

此外,我非常乐意接受更改 FormatInfo 类,甚至 parts 列表,如果有人有建议可以缩短构建时间。由于我主要关心的是从头到尾的顺序迭代时间,也许 LinkedList 会更好吗?

Also, I'm very open to changing the FormatInfo class and even the parts List if anyone has a suggestion that could improve the build time. Since my primary concern is sequential iteration time from front to end maybe a LinkedList would fare better?

嗯...我可以尝试的其他方法是调整测试。我的基准测试非常简单:将姓名组成为 {lastname},{firstname} 格式,并根据区号,前缀,号码和分机号组成格式化的电话号码。这些字符串在字符串中都没有太多的文字段方式。当我考虑原始状态机解析器的工作方式时,我认为这些文字段正是我的代码最有可能做得好的地方,因为我不再需要检查字符串中的每个字符。

Hmm... something else I can try is adjusting my tests. My benchmarks were fairly simple: composing names to a "{lastname}, {firstname}" format and composing formatted phone numbers from the area code, prefix, number, and extension components. Neither of those have much in the way of literal segments within the string. As I think about how the original state machine parser worked, I think those literal segments are exactly where my code has the best chance to do well, because I no longer have to examine each character in the string.

即使我不能使其进步更快,该类仍然有用。只要性能不比基本String.Format()差,它们仍会创建一个强类型接口,该接口允许程序在运行时组装其自己的格式字符串。我需要做的就是公开访问零件清单。

This class is still useful, even if I can't make it go faster. As long as performance is no worse than the base String.Format(), I've still created a strongly-typed interface which allows a program to assemble it's own "format string" at run time. All I need to do is provide public access to the parts list.

推荐答案

这是最终结果:


我在基准测试中更改了格式字符串到应该更适合我的代码的地方:

Here's the final result:

I changed the format string in a benchmark trial to something that should favor my code a little more:


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

The quick brown {0} jumped over the lazy {1}.

正如我所期望的,与原始版本相比,这要好得多;此代码在5.3秒内进行了200万次迭代,而 String.Format 则为6.1秒。这是不可否认的改进。您甚至可能会开始尝试将它用作许多 String.Format 情况的简便替代方法。毕竟,您的工作不会更糟,甚至可能会获得很小的性能提升:提高14%,这没什么可打喷嚏的。

As I expected, this fares much better compared to the original; 2 million iterations in 5.3 seconds for this code vs 6.1 seconds for String.Format. This is an undeniable improvement. You might even be tempted to start using this as a no-brainer replacement for many String.Format situations. After all, you'll do no worse and you might even get a small performance boost: as much 14%, and that's nothing to sneeze at.

除了它。请记住,在专门为支持此代码而设计的情况下,对于200万次尝试,我们仍在谈论不到半秒的差异。除非您很幸运能够在前100个网站上工作,否则即使是忙碌的ASP.Net页面也不会产生那么多的负载。

Except that it is. Keep in mind, we're still talking less than half a second difference for 2 million attempts, under a situation specifically designed to favor this code. Not even busy ASP.Net pages are likely to create that much load, unless you're lucky enough to work on a top 100 web site.

最重要的是,这省略了一个重要的替代方法:您可以每次创建一个新的 StringBuilder ,并使用原始的 Append()手动处理自己的格式。有了这项技术,我的基准测试仅需3.9秒即可完成。这是一个更大的进步。

Most of all, this omits one important alternative: you can create a new StringBuilder each time and manually handle your own formatting using raw Append() calls. With that technique my benchmark finished in only 3.9 seconds. That's a much greater improvement.

总而言之,如果性能不无论如何,您都应该坚持内置选项的清晰度和简单性。但是,在剖析表明这确实在提高性能的情况下,可以通过 StringBuilder.Append()找到更好的选择。

In summary, if performance doesn't matter as much, you should stick with the clarity and simplicity of the built-in option. But when in a situation where profiling shows this really is driving your performance, there is a better alternative available via StringBuilder.Append().

这篇关于性能问题:与String.Format进行比较的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆