-
Notifications
You must be signed in to change notification settings - Fork 862
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
Optimize AWSSDKUtils.ToHex()
for speed and memory
#3293
Conversation
FYI I also added a similar unit test in this draft PR that optimizes UrlEncode that you mention here #3307 There I also removed Netstandard from the unit test since that is not a valid target for unit tests. |
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.
I just stumbled into this because I was tweaking UrlEncode. Great stuff 😍
@@ -39,6 +39,7 @@ | |||
#if NETSTANDARD | |||
using System.Net.Http; | |||
using System.Runtime.InteropServices; | |||
using System.Buffers; |
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 should probably move outside the #if
{ | ||
Func<int, char> converter = lowercase ? (Func<int, char>)ToLowerHex : (Func<int, char>)ToUpperHex; | ||
|
||
for (int i = 0; i < data.Length; i++) |
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.
Out of curiosity have you tried reversing the loop to elide bound checks?
Also could this be a candidate for string.Create
since that would take care of the pooling already?
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.
Thanks for the suggestions @danielmarbach ! Both were very helpful.
#if NETCOREAPP3_1_OR_GREATER | ||
Func<int, char> converter = lowercase ? (Func<int, char>)ToLowerHex : (Func<int, char>)ToUpperHex; | ||
|
||
return string.Create(data.Length * 2, (data, converter), (chars, state) => |
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.
One note: I had to keep this lambda non-static in order to be compatible with <LangVersion>8</LangVersion>
in the csproj.
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 should be changed in my opinion. A reasonable version should at least be 9 even when targeting Netstandard 2.0. That is a good language subset without exposing yourself to weird edge cases. That's how also the Azure .NET SDK treated it until they changed it to an even later version as far as I recall.
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.
Raised #3316
byte[] data = state.data; | ||
Func<int, char> converter = state.converter; | ||
|
||
for (int i = data.Length - 1; i >= 0; i--) |
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.
One note: I found two copies of the loops here were required to get the best performance as they each operate on different types. Trying to funnel both paths through a Span<byte>
to consolidate the code ended up ~10% slower when consumed by .NET Framework.
I'm not sure if any reviewers knew of tricks to get around this without hitting "slow span" paths there.
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.
My experience is that even if you end up with slow downs in the microbenchmarks without a reasonable range due to the massive allocation reductions the overall beneficial effects of getting rid of the allocations especially in a real system using some form of concurrency will outweigh the small latency hits. It is not worth the complexity of maintaining different paths for accommodate for the slow span path. This also takes into account that more modern .NET versions are becoming more and more mainstream and people that are on .NET Framework know what they are not getting.
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.
Thanks for the prompt feedback and advice here @danielmarbach . That makes perfect sense to me.
I've pushed another changeset. My laptop is on battery at the moment so benchmarking will be less reliable, but I can try to run and share them later tonight.
EDIT: It seems the csproj changes mean I now have conflicts. Looks like a rebase is ahead.
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.
Sorry for the delayed response to the PR but impressive performance numbers! I had just a couple comments and discussing with others on the team about the LangVersion
change.
namespace UnitTests.NetStandard.Core | ||
{ | ||
[Trait("Category", "Core")] | ||
public class AWSSDKUtilsTests |
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.
Since the #else
block in the ToHex
method is really just for .NET Framework can you copy the tests to the .NET Framework unit tests in the sdk\test\UnitTests\Custom
folder. Hopefully as part of V4 we can do some test restructure cleanup so we don't have to copy the test between the .NET Framework and .NET Standard+.
hex = hex.Replace("-", string.Empty); | ||
return hex; | ||
} | ||
public static string BytesToHexString(byte[] value) => ToHex(value, false); |
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.
Not sure why we had both ToHex
and BytesToHexString
. Going with old codebase. The BytesToHexString
is only called in AmazonS3ResponseHandler.cs. Since this is v4 I think we can just get rid of this method and update AmazonS3ReponseHandler.
Thanks for the initial feedback @normj . I've found an existing "sdk/test/UnitTests/Custom/Util/AWSSDKUtilsTests.cs" file to add the netfx-side tests to rather than creating a new file but please let me know if you'd instead like it outside of that "Util" folder instead. I look forward to hearing your decision about the As an aside: |
@stevenaw That is fine putting the tests in |
Thanks for the confirmation @normj and great to hear about |
@stevenaw Lets not expand your PR with generator changes for |
PR looks good. Getting another team member to do another pass before merging. |
Thanks for the perf PR! |
Description
Optimize uppercase and lowercase versions of
ToHex()
across all runtimes. Use the built-in option for uppercase on .NET8. Improved speed + memory, particularly for uppercase on .NET8. There's still more room for improvement for the other scenarios but I decided to keep it simple and follow the existing bit twiddling patterns I saw in UrlEncode in the fileMotivation and Context
Testing
Added unit tests for upper + lower case prior to making change. Ran them at the end to validate the new implementation passed. Ran benchmarks against netcoreapp3.1 and net8.0
Screenshots (if appropriate)
Benchmark Code
Types of changes
Checklist
License