【发布时间】:2009-04-17 16:23:37
【问题描述】:
不久前 Jon Skeet 的一篇帖子在我的脑海中植入了构建 CompiledFormatter 类的想法,用于循环使用而不是 String.Format()。
这个想法是对String.Format() 的调用部分用于解析格式字符串是开销;我们应该能够通过将代码移出循环来提高性能。当然,诀窍是新代码应该完全匹配String.Format()的行为。
这周我终于做到了。我使用.Net framework source provided by Microsoft 直接调整了他们的解析器(事实证明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);
}
附加条款:
我对为我的构造函数提供源代码持谨慎态度,因为我不确定我依赖原始 .Net 实现所带来的许可影响。但是,任何想要对此进行测试的人都可以将相关的私有数据公开并分配模仿特定格式字符串的值。
另外,如果有人提出可以改进构建时间的建议,我非常愿意更改 FormatInfo 类,甚至是 parts 列表。由于我主要关心的是从前端到后端的顺序迭代时间,也许LinkedList 会更好?
[更新]:
嗯...我可以尝试的其他方法是调整我的测试。我的基准测试相当简单:将姓名组合成"{lastname}, {firstname}" 格式,并从区号、前缀、号码和分机组件中组合格式化的电话号码。这些都没有太多的字符串中的文字段。当我想到原始状态机解析器的工作方式时,我认为这些文字段正是我的代码最有可能做得好的地方,因为我不再需要检查字符串中的每个字符。
这个类仍然有用,即使我不能让它更快。只要性能不比基本 String.Format() 差,我仍然创建了一个强类型接口,它允许程序在运行时组装它自己的“格式字符串”。我需要做的就是提供对零件清单的公共访问权限。
【问题讨论】:
标签: c# performance string