{"action":"opened","number":20779,"pull_request":{"url":"https://api.github.com/repos/dotnet/coreclr/pulls/20779","id":228092944,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI4MDkyOTQ0","html_url":"https://github.com/dotnet/coreclr/pull/20779","diff_url":"https://github.com/dotnet/coreclr/pull/20779.diff","patch_url":"https://github.com/dotnet/coreclr/pull/20779.patch","issue_url":"https://api.github.com/repos/dotnet/coreclr/issues/20779","number":20779,"state":"open","locked":false,"title":"Optimization: avoid 2 array allocations in GetCustomAtttributes()'s inherited path","user":{"login":"NickCraver","id":454813,"node_id":"MDQ6VXNlcjQ1NDgxMw==","avatar_url":"https://avatars2.githubusercontent.com/u/454813?v=4","gravatar_id":"","url":"https://api.github.com/users/NickCraver","html_url":"https://github.com/NickCraver","followers_url":"https://api.github.com/users/NickCraver/followers","following_url":"https://api.github.com/users/NickCraver/following{/other_user}","gists_url":"https://api.github.com/users/NickCraver/gists{/gist_id}","starred_url":"https://api.github.com/users/NickCraver/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NickCraver/subscriptions","organizations_url":"https://api.github.com/users/NickCraver/orgs","repos_url":"https://api.github.com/users/NickCraver/repos","events_url":"https://api.github.com/users/NickCraver/events{/privacy}","received_events_url":"https://api.github.com/users/NickCraver/received_events","type":"User","site_admin":false},"body":"This is a follow-up to many allocations recognized in #20448.\r\n\r\nA 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](https://github.com/dotnet/coreclr/blob/8b03dbeb90c81b8095b949b44553ff970e169851/src/System.Private.CoreLib/src/System/Reflection/CustomAttribute.cs#L1297-L1299).\r\n\r\nHowever, in the common miss case as simple as:\r\n```c#\r\ntypeof(T).GetCustomAttributes(typeof(TAttr), true);\r\n```\r\n...and many other overloads, all the same path underneath...\r\n\r\nWe 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.\r\n\r\nWhile 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.\r\n\r\nThere are far more wins to be had here, but they require more changes.\r\n\r\nBenchmarks are in flux at the moment (see #20763), but the following benchmark:\r\n```c#\r\n[MemoryDiagnoser]\r\npublic class AttributePerf\r\n{\r\n    [Benchmark]\r\n    public object[] GetAttributesHit() => typeof(AttribuedClass).GetCustomAttributes(typeof(MyAttribute), true);\r\n    [Benchmark]\r\n    public object[] GetAttributesMiss() => typeof(NonAttribuedClass).GetCustomAttributes(typeof(MyAttribute), true);\r\n\r\n    [My(\"Test\")]\r\n    public class AttribuedClass : BaseClass { }\r\n    public class NonAttribuedClass : BaseClass { }\r\n    public class BaseClass { }\r\n\r\n    [AttributeUsage(AttributeTargets.All, AllowMultiple = false)]\r\n    public class MyAttribute : Attribute\r\n    {\r\n        private readonly string _name;\r\n        public MyAttribute(string name) => _name = name;\r\n    }\r\n}\r\n```\r\nYields the following gains with this change:\r\n\r\n```\r\nBenchmarkDotNet=v0.11.2, OS=Windows 10.0.17763.55 (1809/October2018Update/Redstone5)\r\nIntel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores\r\n.NET Core SDK=3.0.100-preview-009722\r\n  [Host]     : .NET Core 3.0.0-preview1-27101-02 (CoreCLR 4.6.27029.02, CoreFX 4.6.27018.01), 64bit RyuJIT\r\n  Job-UWWFJG : .NET Core ? (CoreCLR 4.6.27101.0, CoreFX 4.7.18.53005), 64bit RyuJIT\r\nRuntime=Core  Toolchain=CoreRun\r\n\r\n## Before\r\n            Method |     Mean |     Error |    StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |\r\n------------------ |---------:|----------:|----------:|------------:|------------:|------------:|--------------------:|\r\n  GetAttributesHit | 3.416 us | 0.0953 us | 0.2811 us |      0.1793 |           - |           - |               568 B |\r\n GetAttributesMiss | 1.228 us | 0.0396 us | 0.1169 us |      0.0343 |           - |           - |               112 B |\r\n\r\n## After\r\n            Method |     Mean |     Error |    StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |\r\n------------------ |---------:|----------:|----------:|------------:|------------:|------------:|--------------------:|\r\n  GetAttributesHit | 3.404 us | 0.1109 us | 0.3252 us |      0.1793 |           - |           - |               568 B |\r\n GetAttributesMiss | 1.021 us | 0.0353 us | 0.1026 us |      0.0267 |           - |           - |                88 B |\r\n```\r\n\r\nThe gains are somewhat trivial on a single call (2 arrays and 24 bytes), but this is a path called a lot at scale and hopefully we agree multiplier on these changes is huge.\r\n\r\ncc @benaadams @stephentoub @adamsitnik @noahfalk","created_at":"2018-11-03T01:53:46Z","updated_at":"2018-11-03T01:53:46Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/dotnet/coreclr/pulls/20779/commits","review_comments_url":"https://api.github.com/repos/dotnet/coreclr/pulls/20779/comments","review_comment_url":"https://api.github.com/repos/dotnet/coreclr/pulls/comments{/number}","comments_url":"https://api.github.com/repos/dotnet/coreclr/issues/20779/comments","statuses_url":"https://api.github.com/repos/dotnet/coreclr/statuses/622b74ebb7c3f3dbcd052cab7fef1c4c668ba81d","head":{"label":"NickCraver:craver/reduce-attribute-misses","ref":"craver/reduce-attribute-misses","sha":"622b74ebb7c3f3dbcd052cab7fef1c4c668ba81d","user":{"login":"NickCraver","id":454813,"node_id":"MDQ6VXNlcjQ1NDgxMw==","avatar_url":"https://avatars2.githubusercontent.com/u/454813?v=4","gravatar_id":"","url":"https://api.github.com/users/NickCraver","html_url":"https://github.com/NickCraver","followers_url":"https://api.github.com/users/NickCraver/followers","following_url":"https://api.github.com/users/NickCraver/following{/other_user}","gists_url":"https://api.github.com/users/NickCraver/gists{/gist_id}","starred_url":"https://api.github.com/users/NickCraver/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NickCraver/subscriptions","organizations_url":"https://api.github.com/users/NickCraver/orgs","repos_url":"https://api.github.com/users/NickCraver/repos","events_url":"https://api.github.com/users/NickCraver/events{/privacy}","received_events_url":"https://api.github.com/users/NickCraver/received_events","type":"User","site_admin":false},"repo":{"id":138818976,"node_id":"MDEwOlJlcG9zaXRvcnkxMzg4MTg5NzY=","name":"coreclr","full_name":"NickCraver/coreclr","private":false,"owner":{"login":"NickCraver","id":454813,"node_id":"MDQ6VXNlcjQ1NDgxMw==","avatar_url":"https://avatars2.githubusercontent.com/u/454813?v=4","gravatar_id":"","url":"https://api.github.com/users/NickCraver","html_url":"https://github.com/NickCraver","followers_url":"https://api.github.com/users/NickCraver/followers","following_url":"https://api.github.com/users/NickCraver/following{/other_user}","gists_url":"https://api.github.com/users/NickCraver/gists{/gist_id}","starred_url":"https://api.github.com/users/NickCraver/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NickCraver/subscriptions","organizations_url":"https://api.github.com/users/NickCraver/orgs","repos_url":"https://api.github.com/users/NickCraver/repos","events_url":"https://api.github.com/users/NickCraver/events{/privacy}","received_events_url":"https://api.github.com/users/NickCraver/received_events","type":"User","site_admin":false},"html_url":"https://github.com/NickCraver/coreclr","description":"This repo contains the .NET Core runtime, called CoreCLR, and the base library, called System.Private.Corelib (or mscorlib). It includes the garbage collector, JIT compiler, base .NET data types and many low-level classes. We welcome contributions.","fork":true,"url":"https://api.github.com/repos/NickCraver/coreclr","forks_url":"https://api.github.com/repos/NickCraver/coreclr/forks","keys_url":"https://api.github.com/repos/NickCraver/coreclr/keys{/key_id}","collaborators_url":"https://api.github.com/repos/NickCraver/coreclr/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/NickCraver/coreclr/teams","hooks_url":"https://api.github.com/repos/NickCraver/coreclr/hooks","issue_events_url":"https://api.github.com/repos/NickCraver/coreclr/issues/events{/number}","events_url":"https://api.github.com/repos/NickCraver/coreclr/events","assignees_url":"https://api.github.com/repos/NickCraver/coreclr/assignees{/user}","branches_url":"https://api.github.com/repos/NickCraver/coreclr/branches{/branch}","tags_url":"https://api.github.com/repos/NickCraver/coreclr/tags","blobs_url":"https://api.github.com/repos/NickCraver/coreclr/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/NickCraver/coreclr/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/NickCraver/coreclr/git/refs{/sha}","trees_url":"https://api.github.com/repos/NickCraver/coreclr/git/trees{/sha}","statuses_url":"https://api.github.com/repos/NickCraver/coreclr/statuses/{sha}","languages_url":"https://api.github.com/repos/NickCraver/coreclr/languages","stargazers_url":"https://api.github.com/repos/NickCraver/coreclr/stargazers","contributors_url":"https://api.github.com/repos/NickCraver/coreclr/contributors","subscribers_url":"https://api.github.com/repos/NickCraver/coreclr/subscribers","subscription_url":"https://api.github.com/repos/NickCraver/coreclr/subscription","commits_url":"https://api.github.com/repos/NickCraver/coreclr/commits{/sha}","git_commits_url":"https://api.github.com/repos/NickCraver/coreclr/git/commits{/sha}","comments_url":"https://api.github.com/repos/NickCraver/coreclr/comments{/number}","issue_comment_url":"https://api.github.com/repos/NickCraver/coreclr/issues/comments{/number}","contents_url":"https://api.github.com/repos/NickCraver/coreclr/contents/{+path}","compare_url":"https://api.github.com/repos/NickCraver/coreclr/compare/{base}...{head}","merges_url":"https://api.github.com/repos/NickCraver/coreclr/merges","archive_url":"https://api.github.com/repos/NickCraver/coreclr/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/NickCraver/coreclr/downloads","issues_url":"https://api.github.com/repos/NickCraver/coreclr/issues{/number}","pulls_url":"https://api.github.com/repos/NickCraver/coreclr/pulls{/number}","milestones_url":"https://api.github.com/repos/NickCraver/coreclr/milestones{/number}","notifications_url":"https://api.github.com/repos/NickCraver/coreclr/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/NickCraver/coreclr/labels{/name}","releases_url":"https://api.github.com/repos/NickCraver/coreclr/releases{/id}","deployments_url":"https://api.github.com/repos/NickCraver/coreclr/deployments","created_at":"2018-06-27T02:25:33Z","updated_at":"2018-10-31T00:14:22Z","pushed_at":"2018-11-03T01:45:18Z","git_url":"git://github.com/NickCraver/coreclr.git","ssh_url":"git@github.com:NickCraver/coreclr.git","clone_url":"https://github.com/NickCraver/coreclr.git","svn_url":"https://github.com/NickCraver/coreclr","homepage":"https://github.com/dotnet/core","size":248734,"stargazers_count":0,"watchers_count":0,"language":"C#","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"dotnet:master","ref":"master","sha":"8b03dbeb90c81b8095b949b44553ff970e169851","user":{"login":"dotnet","id":9141961,"node_id":"MDEyOk9yZ2FuaXphdGlvbjkxNDE5NjE=","avatar_url":"https://avatars0.githubusercontent.com/u/9141961?v=4","gravatar_id":"","url":"https://api.github.com/users/dotnet","html_url":"https://github.com/dotnet","followers_url":"https://api.github.com/users/dotnet/followers","following_url":"https://api.github.com/users/dotnet/following{/other_user}","gists_url":"https://api.github.com/users/dotnet/gists{/gist_id}","starred_url":"https://api.github.com/users/dotnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dotnet/subscriptions","organizations_url":"https://api.github.com/users/dotnet/orgs","repos_url":"https://api.github.com/users/dotnet/repos","events_url":"https://api.github.com/users/dotnet/events{/privacy}","received_events_url":"https://api.github.com/users/dotnet/received_events","type":"Organization","site_admin":false},"repo":{"id":30092893,"node_id":"MDEwOlJlcG9zaXRvcnkzMDA5Mjg5Mw==","name":"coreclr","full_name":"dotnet/coreclr","private":false,"owner":{"login":"dotnet","id":9141961,"node_id":"MDEyOk9yZ2FuaXphdGlvbjkxNDE5NjE=","avatar_url":"https://avatars0.githubusercontent.com/u/9141961?v=4","gravatar_id":"","url":"https://api.github.com/users/dotnet","html_url":"https://github.com/dotnet","followers_url":"https://api.github.com/users/dotnet/followers","following_url":"https://api.github.com/users/dotnet/following{/other_user}","gists_url":"https://api.github.com/users/dotnet/gists{/gist_id}","starred_url":"https://api.github.com/users/dotnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dotnet/subscriptions","organizations_url":"https://api.github.com/users/dotnet/orgs","repos_url":"https://api.github.com/users/dotnet/repos","events_url":"https://api.github.com/users/dotnet/events{/privacy}","received_events_url":"https://api.github.com/users/dotnet/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/dotnet/coreclr","description":"This repo contains the .NET Core runtime, called CoreCLR, and the base library, called System.Private.Corelib (or mscorlib). It includes the garbage collector, JIT compiler, base .NET data types and many low-level classes. We welcome contributions.","fork":false,"url":"https://api.github.com/repos/dotnet/coreclr","forks_url":"https://api.github.com/repos/dotnet/coreclr/forks","keys_url":"https://api.github.com/repos/dotnet/coreclr/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dotnet/coreclr/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dotnet/coreclr/teams","hooks_url":"https://api.github.com/repos/dotnet/coreclr/hooks","issue_events_url":"https://api.github.com/repos/dotnet/coreclr/issues/events{/number}","events_url":"https://api.github.com/repos/dotnet/coreclr/events","assignees_url":"https://api.github.com/repos/dotnet/coreclr/assignees{/user}","branches_url":"https://api.github.com/repos/dotnet/coreclr/branches{/branch}","tags_url":"https://api.github.com/repos/dotnet/coreclr/tags","blobs_url":"https://api.github.com/repos/dotnet/coreclr/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dotnet/coreclr/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dotnet/coreclr/git/refs{/sha}","trees_url":"https://api.github.com/repos/dotnet/coreclr/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dotnet/coreclr/statuses/{sha}","languages_url":"https://api.github.com/repos/dotnet/coreclr/languages","stargazers_url":"https://api.github.com/repos/dotnet/coreclr/stargazers","contributors_url":"https://api.github.com/repos/dotnet/coreclr/contributors","subscribers_url":"https://api.github.com/repos/dotnet/coreclr/subscribers","subscription_url":"https://api.github.com/repos/dotnet/coreclr/subscription","commits_url":"https://api.github.com/repos/dotnet/coreclr/commits{/sha}","git_commits_url":"https://api.github.com/repos/dotnet/coreclr/git/commits{/sha}","comments_url":"https://api.github.com/repos/dotnet/coreclr/comments{/number}","issue_comment_url":"https://api.github.com/repos/dotnet/coreclr/issues/comments{/number}","contents_url":"https://api.github.com/repos/dotnet/coreclr/contents/{+path}","compare_url":"https://api.github.com/repos/dotnet/coreclr/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dotnet/coreclr/merges","archive_url":"https://api.github.com/repos/dotnet/coreclr/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dotnet/coreclr/downloads","issues_url":"https://api.github.com/repos/dotnet/coreclr/issues{/number}","pulls_url":"https://api.github.com/repos/dotnet/coreclr/pulls{/number}","milestones_url":"https://api.github.com/repos/dotnet/coreclr/milestones{/number}","notifications_url":"https://api.github.com/repos/dotnet/coreclr/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dotnet/coreclr/labels{/name}","releases_url":"https://api.github.com/repos/dotnet/coreclr/releases{/id}","deployments_url":"https://api.github.com/repos/dotnet/coreclr/deployments","created_at":"2015-01-30T22:10:02Z","updated_at":"2018-11-03T01:07:38Z","pushed_at":"2018-11-03T01:22:30Z","git_url":"git://github.com/dotnet/coreclr.git","ssh_url":"git@github.com:dotnet/coreclr.git","clone_url":"https://github.com/dotnet/coreclr.git","svn_url":"https://github.com/dotnet/coreclr","homepage":"https://github.com/dotnet/core","size":251345,"stargazers_count":10456,"watchers_count":10456,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2502,"mirror_url":null,"archived":false,"open_issues_count":1998,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":2502,"open_issues":1998,"watchers":10456,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dotnet/coreclr/pulls/20779"},"html":{"href":"https://github.com/dotnet/coreclr/pull/20779"},"issue":{"href":"https://api.github.com/repos/dotnet/coreclr/issues/20779"},"comments":{"href":"https://api.github.com/repos/dotnet/coreclr/issues/20779/comments"},"review_comments":{"href":"https://api.github.com/repos/dotnet/coreclr/pulls/20779/comments"},"review_comment":{"href":"https://api.github.com/repos/dotnet/coreclr/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/dotnet/coreclr/pulls/20779/commits"},"statuses":{"href":"https://api.github.com/repos/dotnet/coreclr/statuses/622b74ebb7c3f3dbcd052cab7fef1c4c668ba81d"}},"author_association":"MEMBER","merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":true,"commits":1,"additions":22,"deletions":0,"changed_files":2}}
{"actor":{"display_login":"NickCraver"}}