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.
| 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.
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.
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.
Right -- I'm not aware of patterns where nuint gives better code.
There was a problem hiding this comment.
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.
Would Math.DivRem be more efficient here? Especially once it becomes an intrinsic.
There was a problem hiding this comment.
Would
Math.DivRembe 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.
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.
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.
| 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.
We usually use a seeded Random so the tests are reproducible.
There was a problem hiding this comment.
I agree with Dan, but since the existing tests in this type were already using Security.Cryptography.RandomNumberGenerator it's acceptable to keep it.
adamsitnik
left a comment
There was a problem hiding this comment.
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.
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.
I agree with Dan, but since the existing tests in this type were already using Security.Cryptography.RandomNumberGenerator it's acceptable to keep it.
adamsitnik
left a comment
There was a problem hiding this comment.
LGTM, thank you @hrrrrustic !
Close #78472