C# 性能问题:与String.Format比较
不久前,Jon Skeet的一篇文章在我脑海中灌输了这样一个想法:构建一个C# 性能问题:与String.Format比较,c#,performance,string,C#,Performance,String,不久前,Jon Skeet的一篇文章在我脑海中灌输了这样一个想法:构建一个编译格式化程序类,用于循环中,而不是String.Format() 其思想是调用String.Format()解析格式字符串所花费的部分开销;我们应该能够通过将代码移出循环来提高性能。当然,诀窍在于新代码应该与String.Format()行为完全匹配 这周我终于做到了。我通过使用来直接调整他们的解析器(结果是String.Format()实际上将工作转移到StringBuilder.AppendFormat())。我提出
编译格式化程序
类,用于循环中,而不是String.Format()
其思想是调用String.Format()
解析格式字符串所花费的部分开销;我们应该能够通过将代码移出循环来提高性能。当然,诀窍在于新代码应该与String.Format()
行为完全匹配
这周我终于做到了。我通过使用来直接调整他们的解析器(结果是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);
}
私有类格式化项
{
public int index;//参数列表中项的索引,-1表示它是原始格式字符串中的文本
public char[]值;//原始格式字符串中的文字数据
公共字符串格式;//与提供的参数一起使用的简单格式(即:{0:X}表示十六进制
//对于固定宽度格式(以下示例)
公共int-width;/{0,7}表示它至少应该是7个字符
公共bool justify;//{0,-7}将使用相反的对齐方式
}
//此数据全部由构造函数填充
私有列表零件=新列表();
私有int baseSize=0;
私有字符串格式;
私有IFormatProvider formatProvider=null;
私有ICustomFormatter customFormatter=null;
//这里的代码与String.Format/StringBuilder.AppendFormat方法中的代码非常匹配。
//能快点吗?
公共字符串格式(参数对象[]参数)
{
if(format==null | | args==null)
抛出新ArgumentNullException((format==null)?“format”:“args”);
var sb=新的StringBuilder(基本尺寸);
foreach(格式项目fi部分)
{
如果(fi.index<0)
某人附加(金融价值);
其他的
{
//如果(fi.index>=args.Length)抛出新的FormatException(Environment.GetResourceString(“Format_IndexOutFrange”);
如果(fi.index>=args.Length)抛出新的FormatException(“Format_IndexOutFrange”);
对象arg=args[fi.index];
字符串s=null;
if(customFormatter!=null)
{
s=customFormatter.Format(fi.Format,arg,formatProvider);
}
如果(s==null)
{
if(arg可附加)
{
s=((IFormattable)arg.ToString(fi.format,formatProvider);
}
else if(arg!=null)
{
s=arg.ToString();
}
}
如果(s==null)s=String.Empty;
int pad=fi.宽度-s.长度;
如果(!fi.justify&&pad>0)某人追加(“”,pad);
某人追加;
如果(fi.justify&&pad>0)某人追加(“”,pad);
}
}
使某人返回字符串();
}
//替代实现(用于比较测试)
//单独使用我自己的测试调用String.Format():我不使用它,但它很有用
//我的格式方法如何适合。
公共字符串原始格式(参数对象[]args)
{
返回String.Format(formatProvider、Format、args);
}
补充说明:
我对为构造函数提供源代码持谨慎态度,因为我不确定依赖原始.Net实现会带来什么样的许可影响。但是,任何想要测试这一点的人都可以将相关的私有数据公开,并分配模拟特定格式字符串的值
此外,如果有人提出可以改进构建时间的建议,我非常愿意更改FormatInfo
类,甚至更改部分列表。因为我主要关心的是从前端到后端的顺序迭代时间,所以LinkedList
会更好吗
[更新]:
嗯……我还可以尝试调整我的测试。我的基准测试相当简单:将名称组合成“{lastname},{firstname}”
从区号、前缀、号码和分机组件格式化和组合格式化的电话号码。这两个组件在字符串中都没有太多的文字段。当我思考原始状态机解析器的工作方式时,我认为这些文字段正是我的代码最有可能做好的地方,因为se我不再需要检查字符串中的每个字符
另一个想法是:
这个类仍然很有用,即使我不能让它运行得更快。只要性能不低于基本字符串。Format(),我仍然创建了一个强类型接口,允许程序组装自己的“格式字符串”在运行时。我所需要做的就是提供对部件列表的公共访问。您也考虑过进行JIT编译的时间吗?毕竟,框架将被更新,这可以解释差异?在我看来,为了获得实际的性能改进,您需要考虑任何格式分析将customFormatter和formattable参数转换为一个函数,该函数返回一些数据结构,告诉以后的格式化调用要执行的操作
StringFormatter f = StringFormatter.parse(
"the quick brown {animal} jumped over the {attitude} dog"
);
String s = f.format(myMap);
// After calling obj.toString(), all space characters in the formatted
// object string are converted to underscores.
StringFormatter f = StringFormatter.parse(
"blah blah blah {0:/\\s+/_/} blah blah blah"
);
StringFormatter f = StringFormatter.parse(
"blah blah blah {0:?'NULL'|'NOT NULL'} blah blah blah"
);
// Wraps each elements in single-quote charts, separating
// adjacent elements with a comma.
StringFormatter f = StringFormatter.parse(
"blah blah blah {0:@['$'][,]} blah blah blah"
);