-
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
Convert.Try{From/To}HexString #86556
Conversation
/// <returns>true if the conversion was successful; otherwise, false.</returns> | ||
public static bool TryFromHexString(ReadOnlySpan<char> source, Span<byte> destination, out int bytesWritten) | ||
{ | ||
var length = source.Length; |
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.
var length = source.Length; | |
int length = source.Length; |
Use concrete types here. Same on other places.
Perf-wise this could be nuint
, so all the division and modulo operations are treated as (fast) bit-operations.
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.
Perf-wise this could be nuint, so all the division and modulo operations are treated as (fast) bit-operations.
That's interesting. We rarely use native width types except in interop contexts. @jkotas @EgorBo is it correct that in hot code of this type, it can have perf benefit for simple storage of counters and offsets?
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.
Afair, JIT knows that Span.Length (and Array.Length as well) are never negative so it doesn't need extra efforts from the C# side 🙂 e.g.:
int Foo(Span<int> s) => s.Length % 4;
; Method Foo(System.Span`1[int]):int:this
mov eax, dword ptr [rdx+08H]
and eax, 3
ret
; Total bytes of code: 7
(some of the changes landed in .NET 8.0 so might be not visible on sharplab yet)
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.
Right -- I'm not aware of patterns where nuint gives better code.
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.
JIT knows that Span.Length (and Array.Length as well) are never negative
Thanks for the reminder -- I'll forget sometimes about the new JIT-goodness.
We rarely use native width types except in interop contexts
Quite a lot of vectorized code uses the native width types, because memory operations don't need (sign-) extending moves then (e.g. Unsafe.Add(ref ptr, intVariable)
vs. Unsafe.Add(ref ptr, nuintVariable)
-- https://godbolt.org/z/v8E31Wdsx).
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 Math.DivRem
be more efficient here? Especially once it becomes an intrinsic.
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
Math.DivRem
be more efficient here? Especially once it becomes an intrinsic.
I doubt it, since the JIT optimizes length % 2 != 0
to (length & 1) != 0
.
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.
That comment wasn't just about the single length check, but also the division done a few lines later. DivRem in theory combines the two operations.
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.
the division done a few lines later
The JIT can optimize (uint)length / 2
to (uint)length >> 1
. On X86, DivRem
would likely be implemented using a div
instruction, which is an relatively expensive operation.
went to see whether we have perf coverage and we may be missing the "from hex string" case here. if so might be good to add |
@danmoseley Also created a PR with benchmark |
Tagging subscribers to this area: @dotnet/area-system-runtime Issue DetailsClose #78472
|
return true; | ||
} | ||
|
||
int requiredByteCount = length / 2; |
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.
int requiredByteCount = length / 2; | |
int requiredByteCount = (int)((uint)length / 2); |
The JIT doesn't recognize length
as never negative otherwise.
@EgorBo Should we open a JIT issue?
I'll try. Pushing updates that will probably fail CI since I didn't check them yet, just a checkpoint before sleep 😆 |
@hrrrrustic, we should be able to get this reviewed now. Could you merge in upstream so we can rerun CI? |
Span<char> buffer = stackalloc char[loopCount * 2]; | ||
for (int i = 1; i < loopCount; i++) | ||
{ | ||
byte[] data = Security.Cryptography.RandomNumberGenerator.GetBytes(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.
We usually use a seeded Random so the tests are reproducible.
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 agree with Dan, but since the existing tests in this type were already using Security.Cryptography.RandomNumberGenerator
it's acceptable to keep it.
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.
Overall it LGTM, I found some things that need to be adjusted before merging, but I've simply provided suggestions for all of them and I am about to apply them right now.
@hrrrrustic thank you for your contribution!
if (destination.Length < quotient) | ||
{ | ||
source = source.Slice(0, destination.Length * 2); | ||
quotient = destination.Length; |
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.
subjective nit: using a variable called quotient
to store both "quotient" and "bytesWritten" is confusing to me, we could simply assign bytesWritten
to destination.Length
here
src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs
Outdated
Show resolved
Hide resolved
Span<char> buffer = stackalloc char[loopCount * 2]; | ||
for (int i = 1; i < loopCount; i++) | ||
{ | ||
byte[] data = Security.Cryptography.RandomNumberGenerator.GetBytes(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.
I agree with Dan, but since the existing tests in this type were already using Security.Cryptography.RandomNumberGenerator
it's acceptable to keep it.
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.
LGTM, thank you @hrrrrustic !
Close #78472