Optimization: Reduce many array allocations in GetCustomAtttributes()'s inherited...
authorNick Craver <nrcraver@gmail.com>
Sun, 4 Nov 2018 07:11:47 +0000 (02:11 -0500)
committerJan Kotas <jkotas@microsoft.com>
Sun, 4 Nov 2018 07:11:47 +0000 (00:11 -0700)
commitf28b778e4256662ff31db152d1a1b655a61760a6
tree724f6298953be42575d5961ae7a0df92f6a28f14
parent5ef31f269176be95b404dc2cd5e9253bca2e04ef
Optimization: Reduce many array allocations in GetCustomAtttributes()'s inherited path (dotnet/coreclr#20779)

* Optimization: avoid 2 array allocations in inherited attribute misses

This is a follow-up to many allocations recognized in dotnet/coreclr#20448.

A lot of methods ultimately call through CustomAttributes.GetCustomAttributes(). In the inherited search path (default for most searches above), the inheritance path of a class is traversed, resulting in an array allocation per crawled type, a copy to the overall List<object> and then after that - in current code - an array allocation from that list and a typed array allocation it's copied to.

However, in the common miss case as simple as:
typeof(T).GetCustomAttributes(typeof(TAttr), true);
...and many other overloads, all the same path underneath...

We can avoid the last 2 arrays in the miss case. We have the List<object> to go off of. If that's a zero-entry list, we can return an Array.Empty<TAttr>(). That's effectively what this change does.

While converting the entire attribute pipeline to generics is problematic and has issue since some object[] return abstracts aren't sealed, we can at least somewhat trivially cache an array per attribute type (only one static, ultimately from Array.Empty<T> underneath) and return that for the miss case.

There are far more wins to be had here, but they require more changes.

* Move RuntimeType empty array cache generation to Attribute.CreateAttributeArrayHelper

This exposes Attribute.CreateAttributeArrayHelper to internal and uses it directly on the RuntimeType caching for empty arrays. Though this allocated 1 additional array overall, it's simpler, faster to init, and still is an infinite win over the per-call allocations before this overall changesets.

* CustomAttributes: remove needless array copy in the inherited hit case

This removes a .ToArray() for the sake of Array.Copy() where a simple for loop suffices and removes the allocation. Reversing the "empty" result checks is also just a bit cleaner here.

This also expands the same fix to the MemberInfo path.

Note: should DRY these up too (longstanding issue) - but let's do that in a separate commit for clarity.

* GetCusomAttributes: use ListBuilder<object> for inheritance crawls

This exposes RuntimeType.ListBuilder<T> for internal usage and replaces the List<T> allocation in GetCustomAttributes() paths to reduce allocations and increase performance in the inherited crawl paths (which is the default for many optional-parameter methods in layers above).

Note: there is a subtle behavior depending on previous-null (not possible with a struct now) in AttributeUsageCheck() that I believe still behaves correctly, but could use another set of eyes and a full test suite run to confirm. object[] attributes was removed there simply because it wasn't used before - only cleaning up.

* Attribute caching: use Array.CreateInstance() directly on RuntimeType

This also reverts the CreateAttributeArrayHelper => internal change, since it's no longer needed.

* Ref passing for RuntimeType.ListBuilder<object> & CustomAttribute simplification

This fixes the struct passing duplication and tweaks how we're creating arrays a bit, centralizing the zero-element checks to cover all cases as well as simplify the per-method code to rely on the fact this is happening underneath.

Commit migrated from https://github.com/dotnet/coreclr/commit/6d9fc60df3b721eab98ca08386a1f01358f93839
src/coreclr/src/System.Private.CoreLib/src/System/Reflection/CustomAttribute.cs
src/coreclr/src/System.Private.CoreLib/src/System/RtType.cs