Skip to content

Conversation

@peterrsongg
Copy link
Contributor

Description

This optimization is primarily for windows users. UserCrypto.Decrypt is a hot path for windows machines, and it was doing unnecessary string allocations. Here is the code snippet that was doing the allocations.

List<byte> dataIn = new List<byte>();
for (int i = 0; i < encrypted.Length; i = i + 2)
{
    byte data = Convert.ToByte(encrypted.Substring(i, 2), 16);
    dataIn.Add(data);
}

For each iteration we were: allocating a new 2-char string via Substring(i, 2), call Convert.ToByte(string, 16) which parses that string. So if the hex string is length ~9,760, the loop runs ~4,880 times and creates 4,880 tiny strings. (which was my case). This method is called when decrypting any setting stored in the persistence manager.
call-stack

In newer versions of .NET (.NET 5+) we can just use Convert.FromHexString and do it all in one byte array and much faster.

In my profiling of looping a PutObject call 10 times, I saw the allocations for this specific method go down by 98% and in total by 23%.

Motivation and Context

Performance improvement for windows users

Testing

Dry run passed

Screenshots (if appropriate)

Pre Optimization
user-crypto-optimization
Post Optimization

User-Crypto-post-optimization

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My code follows the code style of this project
  • My change requires a change to the documentation
  • I have updated the documentation accordingly
  • I have read the README document
  • I have added tests to cover my changes
  • All new and existing tests passed

License

  • I confirm that this pull request can be released under the Apache 2 license

@peterrsongg peterrsongg changed the title Reduce object allocation UserCrypto.Decrypt by 98% Reduce object allocations in UserCrypto.Decrypt by 98% for .NET8+ Dec 10, 2025
#if NET8_0_OR_GREATER
dataIn = Convert.FromHexString(encrypted);
#else
// Legacy behavior preserved for older TFMs
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Fix the indentation for the legacy behavior here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

"updateMinimum": true,
"type": "patch",
"changeLogMessages": [
"optimize user crypto decrypt"
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think of using the PR title (Reduce object allocations in UserCrypto.Decrypt by 98% for .NET8+) here instead? I think customers would find that easier to parse :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i like that better, updated :)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes the UserCrypto.Decrypt method to reduce memory allocations by 98% for .NET 8+ users on Windows. The method is a hot path for Windows machines when decrypting settings stored in the persistence manager. The original implementation used String.Substring in a loop to parse hex strings, creating many small string allocations. The optimization uses Convert.FromHexString for .NET 8+ to perform the conversion in a single operation, while preserving the legacy behavior for older target frameworks.

Key Changes:

  • Replaced loop-based hex string parsing with Convert.FromHexString for NET8_0_OR_GREATER
  • Used conditional compilation to maintain backward compatibility with older frameworks
  • Added dev config file with patch version bump and changelog entry

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
sdk/src/Core/Amazon.Runtime/Internal/Settings/UserCrypto.cs Optimized hex string to byte array conversion using Convert.FromHexString for .NET 8+, with conditional compilation to preserve legacy behavior for older TFMs
generator/.DevConfigs/a3466402-916b-4e40-8822-79b28ab5de33.json Added dev config file with patch version bump and changelog message for the core optimization

Comment on lines +41 to +48
// Legacy behavior preserved for older TFMs
List<byte> dataInList = new List<byte>();
for (int i = 0; i < encrypted.Length; i = i + 2)
{
byte data = Convert.ToByte(encrypted.Substring(i, 2), 16);
dataIn.Add(data);
dataInList.Add(data);
}
dataIn = dataInList.ToArray();
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The indentation in the #else block is inconsistent. Lines 41, 42, and 48 use 4 spaces instead of the standard indentation. They should be indented with 12 spaces to align with the surrounding code at the same level as line 39.

Copilot uses AI. Check for mistakes.
@normj
Copy link
Member

normj commented Dec 12, 2025

Out of curiosity do you have any part of your profile stored in the legacy .NET store that uses UserCrypto? I'm wondering why the code even went down this path into .NET legacy store.

@peterrsongg
Copy link
Contributor Author

Out of curiosity do you have any part of your profile stored in the legacy .NET store that uses UserCrypto? I'm wondering why the code even went down this path into .NET legacy store.

Not that i'm aware of? I just used the default profile in ~/.aws/credentials

@peterrsongg
Copy link
Contributor Author

Out of curiosity do you have any part of your profile stored in the legacy .NET store that uses UserCrypto? I'm wondering why the code even went down this path into .NET legacy store.

call-stack-user-crypto

Here is the call stack, looks like it is getting invoked when trying to get the profile from the credentialprofilestorechain, when checking if the user has enabled servicespecificendpoints. Eventually IsUserCryptoAvailable is called which is defined as

        public static bool IsUserCryptAvailable
        {
            get
            {
#if NET8_0_OR_GREATER
                if (!OperatingSystem.IsWindows())
                {
                    return false;
                }
#endif
                if(!_isUserCryptAvailable.HasValue)
                {
                    try
                    {
                        Encrypt("test");
                        _isUserCryptAvailable = true;
                    }
                    catch(Exception e)
                    {
                        var logger = Logger.GetLogger(typeof(UserCrypto));
                        logger.InfoFormat("UserCrypto is not supported.  This may be due to use of a non-Windows operating system or Windows Nano Server, or the current user account may not have its profile loaded. {0}", e.Message);
                        _isUserCryptAvailable = false;
                    }
                }
                return _isUserCryptAvailable.Value;
            }
        }

maybe it's worth looking at the IsUserCryptoAvailable logic?

@peterrsongg peterrsongg merged commit 51a610a into development Dec 12, 2025
3 checks passed
@peterrsongg
Copy link
Contributor Author

merged for now, but i think it's worth looking at why this is getting called

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants