-
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
Reduce code size impact of ArrayPool<T>
#97058
Conversation
Every `SharedArrayPool<T>` brings with it several types, including several array types and all of their interface implementations. This makes the internals of SharedArrayPool a bit less generic to try to reduce that impact. The changes are effectively: - Move the nested Partition, Partitions, and ThreadLocalArray types out from being nested types to being peers - Rename them, since they're no longer inheriting the parent's name - Make them non-generic in terms of Array rather than generic in terms of T[]. These types never index into the array, so other than accessing Length for logging purposes, it could even have used object. - Use Unsafe.As in the two places arrays are dequeued and need to be T[] rather than Array. - Pass in the sizeof(T) rather than using it in the implementation. The size is used only when trimming to determine how much to trim.
Tagging subscribers to this area: @dotnet/area-system-buffers Issue DetailsEvery
From measuring locally, this looks to get back ~120K of the ~200K that came from changing Enumerable.ToArray to use the ArrayPool. I have a separate change that targets ToArray directly, but it's not resulting in the trimming I was expecting and am following up offline.
|
I assume there is no loss in throughput with this change? (Since the original change (#96570) listed perf benchmark results.) |
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.
LGTM!
There was actually a small regression on an ArrayPool microbenchmark. This: using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Buffers;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[DisassemblyDiagnoser]
public class Tests
{
private List<string[]> _arrays = new();
[Benchmark]
public void RentReturn()
{
for (int i = 0; i < 10; i++)
{
_arrays.Add(ArrayPool<string>.Shared.Rent(100));
}
foreach (string[] array in _arrays)
{
ArrayPool<string>.Shared.Return(array);
}
_arrays.Clear();
}
} got ~5-10% slower depending on the run locally. I fixed it with c7b9c24, and that same benchmark is now ~5% faster than main locally. I'm not in love with having to use Unsafe for that, but I'm not aware of another good way to avoid the covariance check. It was previously elided by the JIT because the type in question was sealed, but Array isn't. |
All failures are known. |
* Reduce code size impact of `ArrayPool<T>` Every `SharedArrayPool<T>` brings with it several types, including several array types and all of their interface implementations. This makes the internals of SharedArrayPool a bit less generic to try to reduce that impact. The changes are effectively: - Move the nested Partition, Partitions, and ThreadLocalArray types out from being nested types to being peers - Rename them, since they're no longer inheriting the parent's name - Make them non-generic in terms of Array rather than generic in terms of T[]. These types never index into the array, so other than accessing Length for logging purposes, it could even have used object. - Use Unsafe.As in the two places arrays are dequeued and need to be T[] rather than Array. - Pass in the sizeof(T) rather than using it in the implementation. The size is used only when trimming to determine how much to trim. * Fix throughput regression from covariance check
Every
SharedArrayPool<T>
brings with it several types, including several array types and all of their interface implementations. This makes the internals of SharedArrayPool a bit less generic to try to reduce that impact. The changes are effectively:From measuring locally, this looks to get back ~120K of the ~200K that came from changing Enumerable.ToArray to use the ArrayPool. I have a separate change that targets ToArray directly, but it's not resulting in the trimming I was expecting and am following up offline.