-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Set the exact initial capacity of a List literal #99073
Conversation
Tagging subscribers to this area: @dotnet/interop-contrib Issue DetailsIn .NET 7: List<string> s = ["bonjour"];
Console.WriteLine(s.Capacity); Will print After .NET 8, This code prints This PR restores the previous behavior for this scenario.
|
Thanks @jbevain. This seems like a reasonable discussion point. Let me loop in @stephentoub who likely will have a more informed opinion about the intents of the langauge designers and is always looking for excess memory usage. |
Related: dotnet/roslyn#72318 |
For reference, here's what I wrote in an email earlier today when asked in a related conversation why SetCount behaves the way it does:
|
@stephentoub Based on that snippet, I interepret your opinion to mean the compiler should emit the lower allocation pattern if it can be detected rather than changing the API. Is that fair? |
It's more that the behavior for both SetCount and collection literals is as was previously discussed and designed. We could of course choose to revisit. I still think the behavior for SetCount is correct. The knobs exist on the API surface to separately control the count and capacity, so I don't love the idea of SetCount employing a different heuristic for growth than the rest of the API surface area. If you want the capacity to exactly match the count, you can do either: var list = new List<T>(count);
CollectionsMarshal.SetCount(list, count); or: list.Capacity = count;
CollectionsMarshal.SetCount(list, count); If you don't choose a specific capacity, then I think having SetCount employ the same growth scheme as the rest of the That's separate from how the language/compiler choose to lower collection literals. Because the compiler wouldn't always be able to predict whether a developer was going to add more to the list after it was initialized, we'd decided to just do the simple thing and use list's default growth scheme by not having the compiler set the capacity. If we now believe the 99% case is that someone using a collection literal to construct a Just for completeness why this matters, if you do: var list = new List<T>(1) { item };
list.Add(item);
list.Add(item); that's going to allocate a T[] of length 1, a T[] of length 2, and a T[] of length 4. If you instead do: var list = new List<T>() { item };
list.Add(item);
list.Add(item); that's only going to allocate a T[] of length 4. So from a microoptimization perspective, whether additional adds will be performed is relevant to whether setting the capacity is an optimization or deoptimization. |
@jbevain Given Steve's commentary above and the discussion in dotnet/roslyn#72318, I'm going to convert this PR to Draft for now. |
In .NET 7:
Will print
1
, as it will compile to a call to theList(int)
constructor which sets the exact initial capacity, then to aList.Add
.After .NET 8,
This code prints
4
asCollectionsMarshal.SetCount(List,int)
callsList.Grow
.It seems more optimal to create a List with an exact initial capacity. This PR is not be ready as there's a hundred scenarios I didn't consider and no test at the moment :)
Alternatively, this PR could be scrubbed and the C# compiler could also call the List(int) ctor.