Ответ 1
Чтобы ответить на ваш первоначальный вопрос, я думаю, что ответ лежит в (удивительном) инструменте Reflector. Вы используете коллекции объектов IEnumerable, которые затем также вызывают перегрузку одного и того же типа в методе String.Join. Интересно, что эта функция удивительно похожа на вашу функцию Build, поскольку она перечисляет коллекцию и использует построитель строк, что означает, что не нужно заранее знать длину всех строк.
public static string Join<T>(string separator, IEnumerable<T> values)
{
if (values == null)
{
throw new ArgumentNullException("values");
}
if (separator == null)
{
separator = Empty;
}
using (IEnumerator<T> enumerator = values.GetEnumerator())
{
if (!enumerator.MoveNext())
{
return Empty;
}
StringBuilder sb = StringBuilderCache.Acquire(0x10);
if (enumerator.Current != null)
{
string str = enumerator.Current.ToString();
if (str != null)
{
sb.Append(str);
}
}
while (enumerator.MoveNext())
{
sb.Append(separator);
if (enumerator.Current != null)
{
string str2 = enumerator.Current.ToString();
if (str2 != null)
{
sb.Append(str2);
}
}
}
return StringBuilderCache.GetStringAndRelease(sb);
}
}
Кажется, что-то делает с кэшированными StringBuilders, которые я не совсем понимаю, но, вероятно, почему это происходит быстрее из-за некоторой внутренней оптимизации. Поскольку я работаю на ноутбуке, я, возможно, был замечен изменениями состояния управления электропитанием, прежде чем я запустил код с помощью метода "BuildCheat" (избегает удвоения емкости буфера строковых построек), и время удивительно близко к String.Join(IEnumerable) (также выходил за пределы отладчика).
Время сборки = 1264 мс
JoinFormat = 1282ms
JoinConcat = 1108ms
BuildCheat = 1166ms
private static string BuildCheat<T>(
IEnumerable<T> source,
string delimiter,
string prefix,
string suffix)
{
var builder = new StringBuilder(32);
builder = builder.Append(prefix);
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
builder.Append(e.Current);
}
while (e.MoveNext())
{
builder.Append(delimiter);
builder.Append(e.Current);
}
}
builder.Append(suffix);
return builder.ToString();
}
Ответ на заключительную часть вашего вопроса - это то, где вы упоминаете использование FastAllocateString, но, как вы можете видеть, он не вызывается выше в перегруженном методе, который передает IEnumerable, он вызывается только при непосредственном взаимодействии с строками, и это определенно делает цикл через массив строк, чтобы суммировать их длины до создания окончательного вывода.
public static unsafe string Join(string separator, string[] value, int startIndex, int count)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (startIndex < 0)
{
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));
}
if (count < 0)
{
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));
}
if (startIndex > (value.Length - count))
{
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer"));
}
if (separator == null)
{
separator = Empty;
}
if (count == 0)
{
return Empty;
}
int length = 0;
int num2 = (startIndex + count) - 1;
for (int i = startIndex; i <= num2; i++)
{
if (value[i] != null)
{
length += value[i].Length;
}
}
length += (count - 1) * separator.Length;
if ((length < 0) || ((length + 1) < 0))
{
throw new OutOfMemoryException();
}
if (length == 0)
{
return Empty;
}
string str = FastAllocateString(length);
fixed (char* chRef = &str.m_firstChar)
{
UnSafeCharBuffer buffer = new UnSafeCharBuffer(chRef, length);
buffer.AppendString(value[startIndex]);
for (int j = startIndex + 1; j <= num2; j++)
{
buffer.AppendString(separator);
buffer.AppendString(value[j]);
}
}
return str;
}
Просто из интереса я изменил вашу программу, чтобы не использовать generics, и сделал JoinFormat и JoinConcat принимающим простой массив строк (я не мог легко изменить Build, так как он использует перечислитель), поэтому String.Join использует другую реализацию выше, Результаты довольно впечатляющие:
JoinFormat time = 386ms
Время JoinConcat = 226ms
Возможно, вы можете найти решение, которое сделает лучшее из быстрых массивов строк, в то же время используя общие входы...