diff options
author | Stephen Toub <stoub@microsoft.com> | 2022-09-27 18:22:32 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-27 18:22:32 +0300 |
commit | 36bd4286032ac5d1c3e9b95ec31558fbb1c70902 (patch) | |
tree | 44d6132ea1bb6ed51d6106be62dbd697b5618278 /src/libraries | |
parent | 80f723463eee3a3ebdbf3bbe12de6a12e1eeb9d4 (diff) |
Improve IPAddress to/from bytes perf (#75872)
* Improve IPAddress to/from bytes perf
Also cleaned up some unnecessary `!`s with `MemberNotNullWhen`.
* Address PR feedback to use shifts instead of shuffle
And also simplify fallback.
* Avoid non-portable cast for big endian
Diffstat (limited to 'src/libraries')
-rw-r--r-- | src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj | 3 | ||||
-rw-r--r-- | src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs | 108 |
2 files changed, 68 insertions, 43 deletions
diff --git a/src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj b/src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj index 84a5864cac9..b77d6366c7a 100644 --- a/src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj +++ b/src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <ILLinkKeepDepAttributes>false</ILLinkKeepDepAttributes> <!-- See comments in Cookie.cs --> @@ -158,6 +158,7 @@ <Reference Include="System.Memory" /> <Reference Include="System.Runtime" /> <Reference Include="System.Runtime.InteropServices" /> + <Reference Include="System.Runtime.Intrinsics" /> <Reference Include="System.Threading" /> </ItemGroup> </Project> diff --git a/src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs b/src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs index d1fefb09ab0..5579e8a50d7 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; namespace System.Net { @@ -54,11 +55,13 @@ namespace System.Net internal const int NumberOfLabels = IPAddressParserStatics.IPv6AddressBytes / 2; + [MemberNotNullWhen(false, nameof(_numbers))] private bool IsIPv4 { get { return _numbers == null; } } + [MemberNotNullWhen(true, nameof(_numbers))] private bool IsIPv6 { get { return _numbers != null; } @@ -104,7 +107,7 @@ namespace System.Net /// </devdoc> public IPAddress(long newAddress) { - if (newAddress < 0 || newAddress > 0x00000000FFFFFFFF) + if ((ulong)newAddress > 0x00000000FFFFFFFF) { throw new ArgumentOutOfRangeException(nameof(newAddress)); } @@ -131,18 +134,12 @@ namespace System.Net // Consider: Since scope is only valid for link-local and site-local // addresses we could implement some more robust checking here - if (scopeid < 0 || scopeid > 0x00000000FFFFFFFF) + if ((ulong)scopeid > 0x00000000FFFFFFFF) { throw new ArgumentOutOfRangeException(nameof(scopeid)); } - _numbers = new ushort[NumberOfLabels]; - - for (int i = 0; i < NumberOfLabels; i++) - { - _numbers[i] = (ushort)(address[i * 2] * 256 + address[i * 2 + 1]); - } - + _numbers = ReadUInt16NumbersFromBytes(address); PrivateScopeId = (uint)scopeid; } @@ -151,13 +148,7 @@ namespace System.Net Debug.Assert(numbers != null); Debug.Assert(numbers.Length == NumberOfLabels); - var arr = new ushort[NumberOfLabels]; - for (int i = 0; i < arr.Length; i++) - { - arr[i] = numbers[i]; - } - - _numbers = arr; + _numbers = numbers.ToArray(); PrivateScopeId = scopeid; } @@ -188,17 +179,37 @@ namespace System.Net } else if (address.Length == IPAddressParserStatics.IPv6AddressBytes) { - _numbers = new ushort[NumberOfLabels]; + _numbers = ReadUInt16NumbersFromBytes(address); + } + else + { + throw new ArgumentException(SR.dns_bad_ip_address, nameof(address)); + } + } - for (int i = 0; i < NumberOfLabels; i++) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort[] ReadUInt16NumbersFromBytes(ReadOnlySpan<byte> address) + { + ushort[] numbers = new ushort[NumberOfLabels]; + if (Vector128.IsHardwareAccelerated) + { + Vector128<ushort> ushorts = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(address)).AsUInt16(); + if (BitConverter.IsLittleEndian) { - _numbers[i] = (ushort)(address[i * 2] * 256 + address[i * 2 + 1]); + // Reverse endianness of each ushort + ushorts = Vector128.ShiftLeft(ushorts, 8) | Vector128.ShiftRightLogical(ushorts, 8); } + ushorts.StoreUnsafe(ref MemoryMarshal.GetArrayDataReference(numbers)); } else { - throw new ArgumentException(SR.dns_bad_ip_address, nameof(address)); + for (int i = 0; i < numbers.Length; i++) + { + numbers[i] = BinaryPrimitives.ReadUInt16BigEndian(address.Slice(i * 2)); + } } + + return numbers; } // We need this internally since we need to interface with winsock, @@ -274,12 +285,28 @@ namespace System.Net [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteIPv6Bytes(Span<byte> destination) { - Debug.Assert(_numbers != null && _numbers.Length == NumberOfLabels); - int j = 0; - for (int i = 0; i < NumberOfLabels; i++) + ushort[]? numbers = _numbers; + Debug.Assert(numbers != null && numbers.Length == NumberOfLabels); + + if (BitConverter.IsLittleEndian) + { + if (Vector128.IsHardwareAccelerated) + { + Vector128<ushort> ushorts = Vector128.LoadUnsafe(ref MemoryMarshal.GetArrayDataReference(numbers)); + ushorts = Vector128.ShiftLeft(ushorts, 8) | Vector128.ShiftRightLogical(ushorts, 8); + ushorts.AsByte().StoreUnsafe(ref MemoryMarshal.GetReference(destination)); + } + else + { + for (int i = 0; i < numbers.Length; i++) + { + BinaryPrimitives.WriteUInt16BigEndian(destination.Slice(i * 2), numbers[i]); + } + } + } + else { - destination[j++] = (byte)((_numbers[i] >> 8) & 0xFF); - destination[j++] = (byte)((_numbers[i]) & 0xFF); + MemoryMarshal.AsBytes<ushort>(numbers).CopyTo(destination); } } @@ -365,13 +392,13 @@ namespace System.Net public override string ToString() => _toString ??= IsIPv4 ? IPAddressParser.IPv4AddressToString(PrivateAddress) : - IPAddressParser.IPv6AddressToString(_numbers!, PrivateScopeId); + IPAddressParser.IPv6AddressToString(_numbers, PrivateScopeId); public bool TryFormat(Span<char> destination, out int charsWritten) { return IsIPv4 ? IPAddressParser.IPv4AddressToString(PrivateAddress, destination, out charsWritten) : - IPAddressParser.IPv6AddressToString(_numbers!, PrivateScopeId, destination, out charsWritten); + IPAddressParser.IPv6AddressToString(_numbers, PrivateScopeId, destination, out charsWritten); } public static long HostToNetworkOrder(long host) @@ -429,7 +456,7 @@ namespace System.Net { get { - return IsIPv6 && ((_numbers![0] & 0xFF00) == 0xFF00); + return IsIPv6 && ((_numbers[0] & 0xFF00) == 0xFF00); } } @@ -442,7 +469,7 @@ namespace System.Net { get { - return IsIPv6 && ((_numbers![0] & 0xFFC0) == 0xFE80); + return IsIPv6 && ((_numbers[0] & 0xFFC0) == 0xFE80); } } @@ -455,7 +482,7 @@ namespace System.Net { get { - return IsIPv6 && ((_numbers![0] & 0xFFC0) == 0xFEC0); + return IsIPv6 && ((_numbers[0] & 0xFFC0) == 0xFEC0); } } @@ -464,8 +491,8 @@ namespace System.Net get { return IsIPv6 && - (_numbers![0] == 0x2001) && - (_numbers![1] == 0); + (_numbers[0] == 0x2001) && + (_numbers[1] == 0); } } @@ -474,7 +501,7 @@ namespace System.Net { get { - return IsIPv6 && ((_numbers![0] & 0xFE00) == 0xFC00); + return IsIPv6 && ((_numbers[0] & 0xFE00) == 0xFC00); } } @@ -487,14 +514,11 @@ namespace System.Net { return false; } - for (int i = 0; i < 5; i++) - { - if (_numbers![i] != 0) - { - return false; - } - } - return (_numbers![5] == 0xFFFF); + + ReadOnlySpan<byte> numbers = MemoryMarshal.AsBytes(new ReadOnlySpan<ushort>(_numbers)); + return + MemoryMarshal.Read<ulong>(numbers) == 0 && + BinaryPrimitives.ReadUInt32LittleEndian(numbers.Slice(8)) == 0xFFFF0000; } } @@ -622,7 +646,7 @@ namespace System.Net return this; } - uint address = (uint)_numbers![6] << 16 | (uint)_numbers[7]; + uint address = (uint)_numbers[6] << 16 | (uint)_numbers[7]; return new IPAddress((uint)HostToNetworkOrder(unchecked((int)address))); } |