From 36bd4286032ac5d1c3e9b95ec31558fbb1c70902 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 27 Sep 2022 11:22:32 -0400 Subject: [PATCH] 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 --- .../src/System.Net.Primitives.csproj | 3 +- .../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 84a5864cac902..b77d6366c7a96 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 @@ - + true false @@ -158,6 +158,7 @@ + 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 d1fefb09ab08f..5579e8a50d79d 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.Net.Sockets; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; namespace System.Net { @@ -54,11 +55,13 @@ public class IPAddress 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 @@ private uint PrivateScopeId /// public IPAddress(long newAddress) { - if (newAddress < 0 || newAddress > 0x00000000FFFFFFFF) + if ((ulong)newAddress > 0x00000000FFFFFFFF) { throw new ArgumentOutOfRangeException(nameof(newAddress)); } @@ -131,18 +134,12 @@ public IPAddress(ReadOnlySpan address, long scopeid) // 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 @@ internal IPAddress(ReadOnlySpan numbers, uint scopeid) 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 @@ public IPAddress(ReadOnlySpan address) } 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 address) + { + ushort[] numbers = new ushort[NumberOfLabels]; + if (Vector128.IsHardwareAccelerated) + { + Vector128 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 @@ public bool TryWriteBytes(Span destination, out int bytesWritten) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteIPv6Bytes(Span 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 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(numbers).CopyTo(destination); } } @@ -365,13 +392,13 @@ public long ScopeId public override string ToString() => _toString ??= IsIPv4 ? IPAddressParser.IPv4AddressToString(PrivateAddress) : - IPAddressParser.IPv6AddressToString(_numbers!, PrivateScopeId); + IPAddressParser.IPv6AddressToString(_numbers, PrivateScopeId); public bool TryFormat(Span 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 @@ public bool IsIPv6Multicast { get { - return IsIPv6 && ((_numbers![0] & 0xFF00) == 0xFF00); + return IsIPv6 && ((_numbers[0] & 0xFF00) == 0xFF00); } } @@ -442,7 +469,7 @@ public bool IsIPv6LinkLocal { get { - return IsIPv6 && ((_numbers![0] & 0xFFC0) == 0xFE80); + return IsIPv6 && ((_numbers[0] & 0xFFC0) == 0xFE80); } } @@ -455,7 +482,7 @@ public bool IsIPv6SiteLocal { get { - return IsIPv6 && ((_numbers![0] & 0xFFC0) == 0xFEC0); + return IsIPv6 && ((_numbers[0] & 0xFFC0) == 0xFEC0); } } @@ -464,8 +491,8 @@ public bool IsIPv6Teredo get { return IsIPv6 && - (_numbers![0] == 0x2001) && - (_numbers![1] == 0); + (_numbers[0] == 0x2001) && + (_numbers[1] == 0); } } @@ -474,7 +501,7 @@ public bool IsIPv6UniqueLocal { get { - return IsIPv6 && ((_numbers![0] & 0xFE00) == 0xFC00); + return IsIPv6 && ((_numbers[0] & 0xFE00) == 0xFC00); } } @@ -487,14 +514,11 @@ public bool IsIPv4MappedToIPv6 { return false; } - for (int i = 0; i < 5; i++) - { - if (_numbers![i] != 0) - { - return false; - } - } - return (_numbers![5] == 0xFFFF); + + ReadOnlySpan numbers = MemoryMarshal.AsBytes(new ReadOnlySpan(_numbers)); + return + MemoryMarshal.Read(numbers) == 0 && + BinaryPrimitives.ReadUInt32LittleEndian(numbers.Slice(8)) == 0xFFFF0000; } } @@ -622,7 +646,7 @@ public IPAddress MapToIPv4() 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))); }