-
Notifications
You must be signed in to change notification settings - Fork 553
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace String concat with StringBuilder in Cluster Nodes #994
base: main
Are you sure you want to change the base?
Replace String concat with StringBuilder in Cluster Nodes #994
Conversation
Ideally, there would be no need to create strings at all, or use stringbuilder, if this is a very common operation. We could directly write to a session-local scratch buffer that is reusable for that session, and write to it a sequence of the usual style of writing: while (!RespWriteUtils.TryWriteXxx(...))
... Here is a rough (non-working) prototype of this for However, the |
Your approach seems more performant. However, since it's relatively more code and needs few interface changes, maybe string builder is okay to start with for simplicity and more gains compared to main code. We can optimize further with scratchbuffer if needed? |
Yeah this is at least better than main. |
return nodeInfoStringBuilder | ||
.Append(workers[workerId].Nodeid).Append(" ") | ||
.Append(workers[workerId].Address).Append(":").Append(workers[workerId].Port) | ||
.Append("@").Append(workers[workerId].Port + 10000).Append(",").Append(workers[workerId].hostname).Append(" ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use .Append(char c)
for all single characters such as ' ', ':', etc.
.Append(info.pong).Append(" ") | ||
.Append(workers[workerId].ConfigEpoch).Append(" ") | ||
.Append(info.connected || workerId == 1 ? "connected" : "disconnected") | ||
.Append(GetSlotRange(workerId)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass the current StringBuilder to GetSlotRange
and GetSpecialStates
so we are not creating new string builders or intermediate strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e. we can call GetSlotRange(workerId, nodeInfoStringBuilder)
here.
for (ushort i = 1; i <= NumWorkers; i++) | ||
{ | ||
var info = default(ConnectionInfo); | ||
_ = clusterProvider?.clusterManager?.GetConnectionInfo(workers[i].Nodeid, out info); | ||
nodes += GetNodeInfo(i, info); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Send this StringBuilder to private void GetNodeInfo(ushort workerId, ConnectionInfo info, StringBuilder sb)
so it can append to it.
And implement the public string GetNodeInfo(ushort workerId, ConnectionInfo info)
using the common method, as something like:
var sb = new StringBuilder()
GetNodeInfo(..., sb);
return sb.ToString();
StackExchange.Redis does periodic CLUSTER NODES and for large clusters a lot of allocations can happen with such calls.
We recently saw an issue where lots of allocation increased our .NET committed memory and also GC activity when new clients would be deployed/control plane would probe nodes where cluster size was ~100 nodes.
Using StringBuilder in an adhoc BDN test shows we can get significant improvements using this
| TestMultiStringConcat | None | 125.13 us | 1.894 us | 2.395 us | 93.5059 | 4.3945 | 1567640 B |
| TestMultiStringConcatBuilder | None | 41.59 us | 0.627 us | 0.586 us | 9.5215 | 0.9155 | 160152 B |