I am browsing the source code of the Open Source SignalR project, and I see this diff code which is entitled “Don’t use StringBuilder or foreach in this hot code path” :
- public static string MakeCursor(IEnumerable<Cursor> cursors)
+ public static string MakeCursor(IList<Cursor> cursors)
{
- var sb = new StringBuilder();
- bool first = true;
- foreach (var c in cursors)
+ var result = "";
+ for (int i = 0; i < cursors.Count; i++)
{
- if (!first)
+ if (i > 0)
{
- sb.Append('|');
+ result += '|';
}
- sb.Append(Escape(c.Key));
- sb.Append(',');
- sb.Append(c.Id);
- first = false;
+ result += Escape(cursors[i].Key);
+ result += ',';
+ result += cursors[i].Id;
}
- return sb.ToString();
+ return result;
}
I understand why foreach could be less efficient sometimes, and why it is replaced by for.
However, I learned and experienced that the StringBuilder is the most efficient way to concatenate strings. So I am wondering why the author decided to replace it with standard concatenation.
What’s wrong in here and in general about using StringBuilder ?
I made the code change and yes it made a huge difference in number of allocations (GetEnumerator()) calls vs not. Imagine this code is millions of times per second. The number of enumerators allocated is ridiculous and can be avoided.
edit:
We now invert control in order to avoid any allocations (writing to the writer directly):
https://github.com/SignalR/SignalR/blob/2.0.2/src/Microsoft.AspNet.SignalR.Core/Messaging/Cursor.cs#L36