Skip to content

Conversation

@stephentoub
Copy link
Member

The current implementation of ObjectCollection<T> wraps a List<T> and derives from Collection<T>. This means that every ObjectCollection<T> allocated also involves an extra List<T> object (and its array if items are added to it) as well as multiple levels of indirection on each operation.

We can instead just implement ObjectCollection<T> directly. Since most uses end up with just a single object contained (e.g. for a MedaTypeHeaderValue's CharSet), and since it only ever stores Ts that are non-null classes, we can use the items field to be either a T or a T[], optimizing for the case where a single element is stored. In implementing it directly, we then also avoid the extra List<T> object allocation, as well as the interface dispatch that results from going through the IList<T> interface.

This shows up in simple HTTP requests because many requests end up sending down a Content-Type response header, often including a charset, and if headers are enumerated, that results in a MediaTypeHeaderValue being created.

[Benchmark]
public void Create() => new MediaTypeHeaderValue("application/json").CharSet = "utf-8";
Method Toolchain Mean Error StdDev Ratio Allocated
Create \master\corerun.exe 129.38 ns 0.958 ns 0.849 ns 1.00 184 B
Create \pr\corerun.exe 95.33 ns 0.330 ns 0.258 ns 0.74 104 B

cc: @scalablecory, @davidsh

…n / interface dispatch

The current implementation of `ObjectCollection<T>` wraps a `List<T>` and derives from `Collection<T>`.  This means that every `ObjectCollection<T>` allocated also involves an extra `List<T>` object (and its array if items are added to it) as well as multiple levels of indirection on each operation.

We can instead just implement `ObjectCollection<T>` directly.  Since most uses end up with just a single object contained (e.g. for a `MedaTypeHeaderValue`'s `CharSet`), and since it only ever stores `T`s that are non-null classes, we can use the items field to be either a `T` or a `T[]`, optimizing for the case where a single element is stored.  In implementing it directly, we then also avoid the extra `List<T>` object allocation, as well as the interface dispatch that results from going through the `IList<T>` interface.
@stephentoub stephentoub added this to the 5.0 milestone Apr 13, 2020
@ghost
Copy link

ghost commented Apr 13, 2020

Tagging @dotnet/ncl as an area owner. If you would like to be tagged for a label, please notify danmosemsft.

Copy link
Contributor

@scalablecory scalablecory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good; have you also benchmarked the >1 item case?

@stephentoub
Copy link
Member Author

Code looks good; have you also benchmarked the >1 item case?

Yup:

[Params(1, 2, 4, 8)]
public int Parameters { get; set; }

[Benchmark]
public void Create()
{
    var m = new MediaTypeHeaderValue("application/json");
    for (int i = 0; i < Parameters; i++)
    {
        m.Parameters.Add(new NameValueHeaderValue(i.ToString(), "value"));
    }
}
Method Toolchain Parameters Mean Error StdDev Ratio Allocated
Create \master\corerun.exe 1 122.27 ns 0.615 ns 0.513 ns 1.00 184 B
Create \pr\corerun.exe 1 88.70 ns 0.840 ns 0.744 ns 0.73 104 B
Create \master\corerun.exe 2 167.39 ns 0.737 ns 0.653 ns 1.00 216 B
Create \pr\corerun.exe 2 140.42 ns 0.810 ns 0.676 ns 0.84 192 B
Create \master\corerun.exe 4 265.23 ns 1.504 ns 1.334 ns 1.00 280 B
Create \pr\corerun.exe 4 237.22 ns 4.193 ns 3.717 ns 0.89 256 B
Create \master\corerun.exe 8 496.14 ns 8.661 ns 9.974 ns 1.00 496 B
Create \pr\corerun.exe 8 442.42 ns 8.362 ns 8.213 ns 0.89 472 B

@stephentoub stephentoub merged commit f380910 into dotnet:master Apr 13, 2020
@stephentoub stephentoub deleted the reimplementobjectcollection branch April 13, 2020 21:19
@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants