-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Performance improvements in the CryptoConfig class. CreateFromName is about 10x faster with this change. #39600
Performance improvements in the CryptoConfig class. CreateFromName is about 10x faster with this change. #39600
Conversation
// Check to see if we have an application defined mapping | ||
lock (s_InternalSyncObject) |
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.
This lock is really bad and causes contention issues when our web app is running on E20 Azure VMs.
Perfview:
Name
clr!??AwareLock::Contention
- clr!JITutil_MonContention
|+ mscorlib.ni!CryptoConfig.CreateFromName
||+ mscorlib.ni!Utils.ObjToHashAlgorithm
|||+ mscorlib.ni!RSACryptoServiceProvider.VerifyData
||| + microsoft.identitymodel.tokens!AsymmetricSignatureProvider.Verify
||| + system.identitymodel.tokens.jwt!JwtSecurityTokenHandler.ValidateSignature
||| |+ system.identitymodel.tokens.jwt!JwtSecurityTokenHandler.ValidateSignature
||| | + system.identitymodel.tokens.jwt!JwtSecurityTokenHandler.ValidateToken
||| | + microsoft.teamfoundation.framework.server!OAuth2AuthenticationService.ValidateToken
||| | + microsoft.teamfoundation.framework.server!OAuth2AuthenticationModule.OnAuthenticateRequest
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.
@sebastienros do we have any benchmark in the ASP.NET Perf lab that uses CryptoConfig.CreateFromName
internally?
@@ -358,6 +347,12 @@ public static object CreateFromName(string name, params object[] args) | |||
{ | |||
retvalType = null; | |||
} | |||
|
|||
if (retvalType != null) |
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.
This is critical changes that improves perf by about 4-5x.
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.
would it make sense to add this to appNameHT instead and then make DefaultNameHT regular dictionary?
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.
Good idea. I made a change. I see that @adamsitnik implemented a different fix for the issue. See #40184
We might want to take his change instead of this one.
@VladimirKhvostov Would it be better to pass HashAlgorithm to RSACryptoServiceProvider.VerifyData instead of string? I suspsect that should give you much better perf results |
private static volatile Dictionary<string, Type> appNameHT = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase); | ||
private static volatile Dictionary<string, string> appOidHT = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | ||
private static volatile Dictionary<string, string> s_defaultOidHT; | ||
private static volatile ConcurrentDictionary<string, object> s_defaultNameHT; |
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.
now we will have 3 locks meaning that each of the dictionaries is thread safe but overall state might be torn, @bartonjs is that something we should be concerned about?
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.
s_defaultNameHT is only ever built once (though with this change it looks like now it upgrades itself for delay-loaded type references).
It's actually a bit unfortunate that it needs to be a ConcurrentDictionary at all, but if a major point of this change is to overwrite the strings with Type objects for forward-references then this is the way to go.
But since that upgrade is just a memoization cache on non-user controlled data, there's no state to really be torn, it's "eventually consistent".
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.
sounds good - I'm not familiar with the code, AddAlgorithm here suggested there might be more here than just the cache
private static volatile Dictionary<string, string> appOidHT = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | ||
private static volatile Dictionary<string, string> s_defaultOidHT; | ||
private static volatile ConcurrentDictionary<string, object> s_defaultNameHT; | ||
private static volatile ConcurrentDictionary<string, Type> appNameHT = new ConcurrentDictionary<string, Type>(StringComparer.OrdinalIgnoreCase); |
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.
Should these be readonly
instead of volatile
?
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.
Seems reasonable to me. I can make the change. I was trying to make minimal changes:)
I sort of agree: the best win here would be to fix |
I agree that it would be even better if we could fix the caller. I believe this is the code is here: Line 203:
Do you have a recommendation about what needs to be changed in that code (I am from Azure DevOps team - we do not own this library, but I can sync up with maintainers). At the same time, changing caller will not fix perf regression in this class (compared to full .NET Framework) and will also not fix contention issue, which is in both .NET Framework and .NET core versions. |
Sorry. I clicked on Close Comment - I am new to GitHub. I am using Azure DevOps most of the time. |
- return _rsa.VerifyData(input, hash, signature);
+ return _rsa.VerifyData(input, signature, theCorrectHashAlgorithmNameValue, RSASignaturePadding.Pkcs1); |
b742ee3
to
74acb64
Compare
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.
Why does s_defaultNameHT
need to be concurrent? It seems it is only written to before being published.
@VladimirKhvostov thank you for your PR. we are currently triaging open PRs on corefx. Seems like this one wasn't active for a couple of weeks. |
The corefx/src/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs Line 360 in 74acb64
|
…olatile. Made a change to specify initial capacity when creating DefaultOidHT and DefaultNameHT dictionaries.
74acb64
to
11d8f1a
Compare
src/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs
Outdated
Show resolved
Hide resolved
{ | ||
appNameHT[name] = algorithm; | ||
} | ||
appNameHT[name] = algorithm; |
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.
lock inside of the loop is I guess fine since this is called relatively rarely and presumably once per process
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.
This method should not be called very often, but someone can write the code to call it on every request.
The reason I removed lock here is because I really needed to remove lock in the CreateFromName method, which causes contention. See line 336.
Generally looks good to me, one comment: #39600 (comment) |
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.
See offline thread
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.
We need to figure out if we want to merge some of the @adamsitnik's stuff into here or reverse
Will this get backported to .NET? I am still seeing this issue in mscorlib 4.8.4180.0; I am referring to following lock contention
|
No. .NET Framework is only receiving critical fixes at this point (https://devblogs.microsoft.com/dotnet/net-core-is-the-future-of-net/). If lock contention in MapNameToOID is showing up for you, my recommendation would be to try to eliminate calls to it, if you can. For example, instead of |
@bartonjs Thanks for quick response; currently this is being triggered from System.IdentityModel:
|
CryptoConfig.CreateFromName in the dotnet core is about 4.5 times slower than the same method in .NET
I wrote a simple app:
My desktop machine has i7-7820X CPU.
Here are results: