-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Right-size Lists when created #23714
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
Conversation
dab5b2d to
766aa6f
Compare
|
Interesting benchmarks, however these are super micro-optimizations and make the code a bit uglier to look at. We should check which of these (if any) are per request and consider keeping only those ones and drop the rest of the changes. |
Yeah, it's not the prettiest thing once the comments explaining it get added.
Yep, happy to cull anything not considered impactful enough to preserve the readability. I started with a Find All and reviewed all the non-test usage, and already pre-culled a few of them that looked like start-up one-offs to me. I've been having a few issues with the build locally on my laptop, so pushed it up as a draft to start with to make sure it built correctly and I hadn't broken anything in the tests. |
|
Whenever we're initializing a collection from a loop, pre-initializing the size is a good improvement. I would rather not initialize collections to 1/2/3/4 sizes because it doesn't add much, and it adds a small maintainability burden. If we change the number of items manually added to the collection then we need to remember to change the initial capacity size. We should only make that sort of micro-optimization if it is on a hot-path (per-request) |
|
Anything either of you would like me to do on this before I un-draft it? I'm just conscious that with changes in quite a few files over the repo it will ping quite a few code owners for review 😅 |
|
Quick minor note, any instances of |
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'd change these and the following in the next files to the
mediaTypes ??= new List<string>(/* ... */)pattern when you're on it.
src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs
Outdated
Show resolved
Hide resolved
766aa6f to
e8ae502
Compare
|
@pranavkm Anything specific you'd like me to edit on this PR before hitting the Ready for review button? |
|
The few times that I've tried initializing collections with the right size, I hadn't noticed any significant performance improvement in application throughput \ allocation profiles. But it would be uncool to now get this in since you've already made the changes. @halter73 \ @BrennanConroy thoughts on this? |
|
I think we mostly wanted to scope the change down to initializing the collection with a size when we know how many elements are being added, such as loops |
|
Ok cool, I'll edit the changes soon to just do the capacities when the number of items is known 👍 |
Create new instances of List<T> with an appropriate capacity for the items that will be added. Use Array.Empty<T>() where appropriate, rather than create an empty list and then return it.
e8ae502 to
42df132
Compare
BrennanConroy
left a comment
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.
SignalR looks good except for the one comment
src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocolWorker.cs
Outdated
Show resolved
Hide resolved
…ackHubProtocolWorker.cs Co-authored-by: Brennan <[email protected]>
src/Razor/Razor/src/TagHelpers/ReadOnlyTagHelperAttributeList.cs
Outdated
Show resolved
Hide resolved
| if (_bufferList == null) | ||
| { | ||
| _bufferList = new List<ArraySegment<byte>>(); | ||
| _bufferList = new List<ArraySegment<byte>>((int)buffer.Length); |
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.
Could we revert this change? The usage is a little different from other changes (we're pre-allocating the list based on the number of spans in a sequence) and I worry there might be security implications to this.
Revert the change to set the capacity of the list.
Use Array.Empty<TagHelperAttribute>() in two places. Remove static readonly field containing zero-length array.
Co-authored-by: Pranav K <[email protected]>
|
Thanks for the PR! |
Use Array.Empty<T>() instead of creating a new list.
Revert two changes to use Array.Empty<T>(), as it breaks things even though the class says it's read-only...
|
Any more feedback on this? Looks like the failing test is just some Selenium flakiness. |
|
Thanks again! |
Changes
List<T>with an appropriate capacity for the items that will be added.Array.Empty<T>()where appropriate, rather than create an empty list and then return it.Create lists with the default capacity for a list containing only a small number of items, which is 4, so that a resize is not immediately required when the first item is added.Rationale
I noticed there were a few places where lists were created and then immediately added to, so thought that the capacity could be specified instead. In many cases the lists have the potential to be added to again later after the method creating them returns, so I've used the default capacity the
Lists would have had before the change once members were added by the code, rather than the exact number of items, as otherwise any resizes that may have happened would then happen sooner than before. Similarly this might also have changed when the resizes caused the arrays to double in size, so increasing the memory footprint.I've separate this PR into two commits as the second commit uses
4as a magic number quite a lot. I've put a comment on each instance of its use explaining why, but if the change is wanted there might be a better way to do it than sprinkling4everywhere.I used the following micro-benchmarks to guide the use of 4, rather than say 1, as the default capacity.
As you can see from the
*ThenAddOncebenchmarks, adding 1 item to an empty list allocates the same memory as adding 1 item to a list with a capacity of 4, but it does so ~37% faster due to there being no need to resize the internal array from 0 to 4 when the first item is added.While only setting a capacity of 1 saves 8 bytes compared to initial capacities of 0 and 4, it doubles the time required to add a second item later, as well as requiring an allocation of an extra 24 bytes, as can be seen by the
*ThenAddTwicebenchmarks.Overall,
CreateWithFour*withnew List<T>(4)seems the best option as it uses the same memory asnew List<T>(), while being faster for adding 1 and 2 items usingAdd().Benchmarks