Skip to content

Commit

Permalink
Improve IPAddress to/from bytes perf (#75872)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
stephentoub committed Sep 27, 2022
1 parent 80f7234 commit 36bd428
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -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 -->
Expand Down Expand Up @@ -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>
108 changes: 66 additions & 42 deletions src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;

namespace System.Net
{
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -104,7 +107,7 @@ private uint PrivateScopeId
/// </devdoc>
public IPAddress(long newAddress)
{
if (newAddress < 0 || newAddress > 0x00000000FFFFFFFF)
if ((ulong)newAddress > 0x00000000FFFFFFFF)
{
throw new ArgumentOutOfRangeException(nameof(newAddress));
}
Expand All @@ -131,18 +134,12 @@ public IPAddress(ReadOnlySpan<byte> 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;
}

Expand All @@ -151,13 +148,7 @@ internal IPAddress(ReadOnlySpan<ushort> 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;
}

Expand Down Expand Up @@ -188,17 +179,37 @@ public IPAddress(ReadOnlySpan<byte> 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<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,
Expand Down Expand Up @@ -274,12 +285,28 @@ public bool TryWriteBytes(Span<byte> destination, out int bytesWritten)
[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);
}
}

Expand Down Expand Up @@ -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<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)
Expand Down Expand Up @@ -429,7 +456,7 @@ public bool IsIPv6Multicast
{
get
{
return IsIPv6 && ((_numbers![0] & 0xFF00) == 0xFF00);
return IsIPv6 && ((_numbers[0] & 0xFF00) == 0xFF00);
}
}

Expand All @@ -442,7 +469,7 @@ public bool IsIPv6LinkLocal
{
get
{
return IsIPv6 && ((_numbers![0] & 0xFFC0) == 0xFE80);
return IsIPv6 && ((_numbers[0] & 0xFFC0) == 0xFE80);
}
}

Expand All @@ -455,7 +482,7 @@ public bool IsIPv6SiteLocal
{
get
{
return IsIPv6 && ((_numbers![0] & 0xFFC0) == 0xFEC0);
return IsIPv6 && ((_numbers[0] & 0xFFC0) == 0xFEC0);
}
}

Expand All @@ -464,8 +491,8 @@ public bool IsIPv6Teredo
get
{
return IsIPv6 &&
(_numbers![0] == 0x2001) &&
(_numbers![1] == 0);
(_numbers[0] == 0x2001) &&
(_numbers[1] == 0);
}
}

Expand All @@ -474,7 +501,7 @@ public bool IsIPv6UniqueLocal
{
get
{
return IsIPv6 && ((_numbers![0] & 0xFE00) == 0xFC00);
return IsIPv6 && ((_numbers[0] & 0xFE00) == 0xFC00);
}
}

Expand All @@ -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<byte> numbers = MemoryMarshal.AsBytes(new ReadOnlySpan<ushort>(_numbers));
return
MemoryMarshal.Read<ulong>(numbers) == 0 &&
BinaryPrimitives.ReadUInt32LittleEndian(numbers.Slice(8)) == 0xFFFF0000;
}
}

Expand Down Expand Up @@ -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)));
}

Expand Down

0 comments on commit 36bd428

Please sign in to comment.