Некоторое время назад пост Джона Скита привил в мою голову идею создания класса CompiledFormatter
для использования в цикле вместо String.Format()
.
Идея состоит в том, что часть вызова String.Format()
потраченная на разбор строки формата, является дополнительной; мы должны иметь возможность повысить производительность, переместив этот код за пределы цикла. Хитрость, конечно, в том, что новый код должен точно соответствовать поведению String.Format()
.
На этой неделе я наконец сделал это. Я использовал исходный код .Net, предоставленный 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 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 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(), я по-прежнему создавал строго типизированный интерфейс, который позволяет программе собирать свою собственную "строку формата" во время выполнения. Все, что мне нужно сделать, это предоставить открытый доступ к списку деталей.