-
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
Implement IUtf8SpanParsable
on IPAddress
and IPNetwork
#102144
Implement IUtf8SpanParsable
on IPAddress
and IPNetwork
#102144
Conversation
This is preparation for the work required to enable IUtf8SpanParsable support on IPAddress and IPNetwork. Also replaced a few instances of unsafe code with spans. This uses the same style of generic constraints as the numeric parsing in corelib (although it can't access IUtfChar<TSelf>.) All tests pass, although the UTF8 code paths haven't yet been tested. Some restructuring is pending as I plumb IPAddressParser to the updated generic methods. Future commits will handle the downstream dependencies of IPvXAddressHelper - at present, this looks like Uri and TargetHostNameHelper.
This allows me to eliminate the nested class and now-redundant generic type constraints on each method
These are just the changes required to make System.Private.Uri compile. * Split IPAddressParser and IPv6AddressHelper into multiple files and shared common components between projects. * Updated some of the Uri-specific additions to IPvXAddressHelper files to use spans. * Minor adjustments to project files to support the above.
Removed a using statement in the ref project, small reduction in the csproj diff for System.Net.Primitives
Note regarding the
|
Hi @edwardneal, sorry for the delay in getting to this, I had missed the tag initially since this is under the networking area. It's worth noting that That proposal should be straightforward and I expect have no issues going through API review, at which point this can likely get merged. If you could assist in opening such a proposal, that should help the process go along faster. |
Thanks @tannergooding. Sorry for the confusion here - I read the original issue as approving the addition of |
We want to include all new public APIs and interfaces in the proposal whether that is for
The general spirit behind the feature fits that, but we still do explicit review and sign-off as there are often edge cases that we may warrant different consideration. |
Given that we are blocked on API review, I would recommend to either change the PR to Draft or close it, until the API is approved. @edwardneal what do you prefer and can you please do it? Thanks! |
src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.ConvertInterfaceNameToLuid.cs
Outdated
Show resolved
Hide resolved
} | ||
buffer[buffer.Length - 1] = '\0'; | ||
|
||
if (Interop.IpHlpApi.ConvertInterfaceNameToLuid(buffer, ref interfaceLuid) != 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.
If this produces an error, but in the finally we then Free the native memory, will that end up corrupting whatever error code the caller might need to retrieve?
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.
It won't on Windows: the documentation doesn't say that any of the methods changes the result of GetLastError, and testing confirms this - the return value is the only success/failure response, so there's no error code to corrupt. SetLastError
was set to true on the interop definitions though, I've corrected this.
It will on Unix, so I've wrapped the Free in GetLastPInvokeError/SetLastPInvokeError as per the pattern in CriticalHandle.Cleanup.
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! Left a few comments/questions, but otherwise LGTM.
* SetLastError has the correct value in the Windows interop layer. * Preserve InterfaceNameToIndex's errno in the Unix InterfaceInfoPal.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Net;
public class Bench
{
[Benchmark]
[Arguments("192.168.0.1")]
[Arguments("4294967295")]
[Arguments("037777777777")]
[Arguments("0xff.0x7f.0x20.0x01")]
[Arguments("192.168.0.0/16")]
[Arguments("::192.168.0.1")]
[Arguments("100:0:1:2:0:0:000:abcd")]
[Arguments("Fe08::1%13542")]
[Arguments("1:2:3:4:5:6:7:8::")]
public bool TryParse(string s) => IPAddress.TryParse(s, out _);
[Benchmark]
[Arguments("http://192.168.0.1:123/foo")]
[Arguments("http://[100:0:1:2:0:0:000:abcd]:123/foo")]
public string UriHost(string s) => new Uri(s).Host;
} |
Thanks MihaZupan. That's quite a wide regression in I've reverted the only other difference so that there's a common baseline, and I'll keep looking at this. I'm faintly suspicious that passing around an int rather than a char is causing the JIT to implicitly reintroduce bounds checks, but I wouldn't expect them to have such a large impact in this case. |
Can you point me exactly to the non-elided bound check? Last I checked, all users of |
I agree. [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FromChar(int c)
{
return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c];
}
That's not normally a problem - like you said, Uri.FromHex passes it a char, so when it's inlined it makes perfect sense that the JIT can see which datatype is really being passed around. I'm passing it an integer though, so the bounds check would likely remain. I'm hesitant to say that it's the only cause of the performance regression because when I benchmarked it, the bounds checks only introduced a 0.2ns/call performance gap, even on a slower laptop. I'm going to test the idea over the next few days (edit: this'll likely be the weekend now, sorry.) Updating on this: investigating the performance regressions led to a few places where The UriHost benchmarks were stranger. As best I can tell, Prior to code review
Post code review
Pending any surprises from CI, I'll re-request the benchmark from EgorBot. |
* Replaced int.CreateTruncating with a JIT pattern which converts from TChar to a ushort. * Removed various TChar.CreateTruncating constants, reinstating direct character constants. * Replaced branch statements when parsing IPv4 numerics with a HexConverter lookup. * IPv4 parts stored in an array of longs to improve register usage. * Small improvement in reassembly to eliminate extra array access. * Reverted changes to Uri parsing layer, removing the translation logic between a Span and a pointer for IPv4 addresses.
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.
Thank you
src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Net/NetworkInformation/InterfaceInfoPal.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Net/NetworkInformation/InterfaceInfoPal.Unix.cs
Outdated
Show resolved
Hide resolved
* Replaced ushorts with integers to remove some zero-extensions. * Replaced NativeMemory with an ArrayPool<byte>. * Removed unnecessary assignment to ch.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Net;
public class Bench
{
[Benchmark]
[Arguments("192.168.0.1")]
[Arguments("4294967295")]
[Arguments("037777777777")]
[Arguments("0xff.0x7f.0x20.0x01")]
[Arguments("192.168.0.0/16")]
[Arguments("::192.168.0.1")]
[Arguments("100:0:1:2:0:0:000:abcd")]
[Arguments("Fe08::1%13542")]
[Arguments("1:2:3:4:5:6:7:8::")]
public bool TryParse(string s) => IPAddress.TryParse(s, out _);
[Benchmark]
[Arguments("http://192.168.0.1:123/foo")]
[Arguments("http://[100:0:1:2:0:0:000:abcd]:123/foo")]
public string UriHost(string s) => new Uri(s).Host;
} |
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 sticking through all the reviews, change & perf LGTM
That's great, thanks for your patience @MihaZupan and everyone who reviewed. There are a few optimisations which I want to pick up from the earlier revisions, but I'll definitely re-read the earlier conversations on this PR first - I'm fairly sure I dragged the review process out by missing the point of some of them! |
Closes #103111
Relates to #81500. cc @tannergooding - hopefully this doesn't tread on any prior work.
This implements
IUtf8SpanParsable
onIPAddress
andIPNetwork
. It does so by makingIPAddressParser
generic betweenbyte
andchar
. Doing so needed deeper changes than I'd have liked (the separators aren't considered constants, which means that I had to swap a switch statement and its "goto case" statements for a set of if statements) As I did that, I replaced most of the unsafe code and pointers withReadOnlySpan
s (which eliminates a pattern where downstream code was pinning an ROS and passing its pointer.)IPAddressParser
,IPv4AddressHelper
andIPv6AddressHelper
are shared with System.Net.Quic, System.Net.Security and System.Private.Uri. The changes to the first two projects are pretty minor, consisting of one change toTargetHostNameHelper
. System.Private.Uri is similar, but it makes more extensive use of pointers.There are two differences between this PR and the underlying issue:
Parse(ReadOnlySpan<byte>)
andTryParse(ReadOnlySpan<byte>, out IPNetwork)
toIPNetwork
.IPNetwork
implementsIUtf8SpanParsable
explicitly rather than implicitly.In both cases, this is done to maintain consistency with the
ISpanParsable
members which were already implemented. I've taken this to be covered by the original API review.It's worth noting that
IPv6AddressHelper<TChar>.IsValid
in System.Private.Uri continues to referencechar*
rather thanReadOnlySpan<TChar>
. This is inconsistent, but I've left it alone for now. I'd prefer to address that in a follow-up PR which replaces the unsafe components fromUri.PrivateParseMinimal
and the methods it calls with a ROS.