-
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
ClaimsPrincipal.SelectPrimaryIdentity allocates unnecessary enumerator #107861
Comments
As we reported in dotnet/runtime#107861 it turns out that retrieving ClaimsPrincipal.Identity causes an unnecessary allocation. This works around that.
Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones |
I benchmarked the code with these changes and they show about 75% improvement for /cc @bartonjs or @JamesNK to confirm Full perf numbers and code changes: Perf Results
Test codeusing System.Security.Claims;
using System.Security.Principal;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
new BenchmarkSwitcher(typeof(Program).Assembly).Run(args);
[MemoryDiagnoser(false)]
[HideColumns("Job", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
private IEnumerable<ClaimsIdentity>? _identitiesWithNulls;
private IEnumerable<ClaimsIdentity>? _identitiesWithoutNulls;
private ClaimsPrincipal? _principalWithNulls;
private ClaimsPrincipal? _principalWithoutNulls;
[Params(1, 2, 3, 5, 10)]
public int NumberOfIdentities;
[GlobalSetup]
public void Setup()
{
_identitiesWithNulls = Enumerable.Range(0, NumberOfIdentities - 1).Select(_ => default(ClaimsIdentity)).Concat([new ClaimsIdentity()]);
_identitiesWithoutNulls = Enumerable.Range(0, NumberOfIdentities).Select(_ => new ClaimsIdentity());
_principalWithNulls = new ClaimsPrincipal(_identitiesWithNulls);
_principalWithoutNulls = new ClaimsPrincipal(_identitiesWithoutNulls);
}
[Benchmark]
public IIdentity? GetIdentity_WithNulls() => _principalWithNulls!.Identity;
[Benchmark]
public IIdentity? GetIdentity_WithoutNulls() => _principalWithoutNulls!.Identity;
[Benchmark]
public IIdentity? IdentitySelector_WithNulls() => ClaimsPrincipal.PrimaryIdentitySelector(_identitiesWithNulls!);
[Benchmark]
public IIdentity? IdentitySelector_WithoutNulls() => ClaimsPrincipal.PrimaryIdentitySelector(_identitiesWithoutNulls!);
} Code changeprivate static ClaimsIdentity? SelectPrimaryIdentity(IEnumerable<ClaimsIdentity> identities)
{
ArgumentNullException.ThrowIfNull(identities);
+ if (identities is List<ClaimsIdentity> list)
+ {
+ foreach (ClaimsIdentity identity in list)
+ {
+ if (identity != null)
+ {
+ return identity;
+ }
+ }
+
+ return null;
+ }
+
foreach (ClaimsIdentity identity in identities)
{
if (identity != null)
{
return identity;
}
}
return null;
} |
Description
The
ClaimsPrincipal.SelectPrimaryIdentity
method (which is used by the publicIdentity
property) always allocates an enumerator, when this should never be necessary.The method is declared as taking an
IEnumerable<ClaimsIdentity>
. In normal usage, this argument is always theList<ClaimsIdentity>
from the_identities
field. (There's an obscure situation in which it might not be: user code might retrieve the staticPrimaryIdentitySelector
property and invoke it passing in their ownIEnumerable<ClaimsIdentity>
.)This means that the
foreach
insideSelectPrimaryIdentity
can't take advantage of the zero-allocation enumeration normally possible with aList<T>
, because the CLR has to generate code that can cope with anyIEnumerable<T>
. So the overwhelming majority of use cases pay an allocation penalty that is unnecessary.This could be modified to detect an
IList
as a special case to avoid the allocation.Configuration
.NET 8.0.8, SDK 8.0.400. All architectures, any OS.
Regression?
No.
Analysis
We saw this allocation in a performance analysis. We were able to prevent it by writing this helper:
and using it instead of
user.Identity
. Running our performance analysis again with this in place showed that we no longer saw an enumerator being allocated.The text was updated successfully, but these errors were encountered: