Skip to content
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

Optimise Guard.IsBitwiseEqual #3325

Merged
merged 22 commits into from Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fca15ef
Improve Guard.IsBitwiseEqualTo performance
john-h-k Jun 4, 2020
f0a5ba3
Fix inlining
john-h-k Jun 4, 2020
eea72ab
Merge branch 'master' into improve-guard
john-h-k Jun 4, 2020
1d9e8a4
Fix incorrect bool
john-h-k Jun 4, 2020
6950888
Merge branch 'master' into improve-guard
Kyaa-dost Jun 4, 2020
9184fd2
Update Microsoft.Toolkit/Diagnostics/Guard.Comparable.Generic.cs
john-h-k Jun 4, 2020
717381f
Update Microsoft.Toolkit/Diagnostics/Guard.Comparable.Generic.cs
john-h-k Jun 4, 2020
c463460
Add suggestions
john-h-k Jun 4, 2020
764173b
Merge branch 'master' into improve-guard
michael-hawker Jun 5, 2020
79afc44
Add new tests
john-h-k Jun 5, 2020
705580e
Merge branch 'improve-guard' of github.com:john-h-k/WindowsCommunityT…
john-h-k Jun 5, 2020
e47334a
Merge branch 'master' into improve-guard
john-h-k Jun 5, 2020
90b4d1d
Change from SRCS.Unsafe to unsafe for consistency
john-h-k Jun 5, 2020
5ac3b2f
Get analyzer to shut up
john-h-k Jun 5, 2020
914e4d2
Merge branch 'improve-guard' of github.com:john-h-k/WindowsCommunityT…
john-h-k Jun 5, 2020
62c3c33
Fix silly mistake
john-h-k Jun 5, 2020
32ed95f
Fix formatting
john-h-k Jun 5, 2020
a7a85df
moar formatting
john-h-k Jun 5, 2020
f6a2a6b
Update Microsoft.Toolkit/Diagnostics/Guard.Comparable.Generic.cs
john-h-k Jun 5, 2020
70f975f
Merge branch 'master' into improve-guard
john-h-k Jun 5, 2020
5754bcd
Update Guard.Comparable.Generic.cs
john-h-k Jun 5, 2020
80d464b
Merge branch 'master' into improve-guard
michael-hawker Jun 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
72 changes: 48 additions & 24 deletions Microsoft.Toolkit/Diagnostics/Guard.Comparable.Generic.cs
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
john-h-k marked this conversation as resolved.
Show resolved Hide resolved
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;

#nullable enable
Expand Down Expand Up @@ -103,17 +104,15 @@ public static void IsNotEqualTo<T>(T value, T target, string name)
/// <param name="name">The name of the input parameter being tested.</param>
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not a bitwise match for <paramref name="target"/>.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IsBitwiseEqualTo<T>(T value, T target, string name)
public static unsafe void IsBitwiseEqualTo<T>(T value, T target, string name)
where T : unmanaged
{
// Include some fast paths if the input type is of size 1, 2, 4 or 8.
// Include some fast paths if the input type is of size 1, 2, 4, 8, or 16.
// In those cases, just reinterpret the bytes as values of an integer type,
// and compare them directly, which is much faster than having a loop over each byte.
// The conditional branches below are known at compile time by the JIT compiler,
// so that only the right one will actually be translated into native code.
if (typeof(T) == typeof(byte) ||
typeof(T) == typeof(sbyte) ||
typeof(T) == typeof(bool))
if (sizeof(T) == 1)
{
byte valueByte = Unsafe.As<T, byte>(ref value);
byte targetByte = Unsafe.As<T, byte>(ref target);
Expand All @@ -125,9 +124,7 @@ public static void IsBitwiseEqualTo<T>(T value, T target, string name)

ThrowHelper.ThrowArgumentExceptionForsBitwiseEqualTo(value, target, name);
}
else if (typeof(T) == typeof(ushort) ||
typeof(T) == typeof(short) ||
typeof(T) == typeof(char))
else if (sizeof(T) == 2)
{
ushort valueUShort = Unsafe.As<T, ushort>(ref value);
ushort targetUShort = Unsafe.As<T, ushort>(ref target);
Expand All @@ -139,9 +136,7 @@ public static void IsBitwiseEqualTo<T>(T value, T target, string name)

ThrowHelper.ThrowArgumentExceptionForsBitwiseEqualTo(value, target, name);
}
else if (typeof(T) == typeof(uint) ||
typeof(T) == typeof(int) ||
typeof(T) == typeof(float))
else if (sizeof(T) == 4)
{
uint valueUInt = Unsafe.As<T, uint>(ref value);
uint targetUInt = Unsafe.As<T, uint>(ref target);
Expand All @@ -153,37 +148,66 @@ public static void IsBitwiseEqualTo<T>(T value, T target, string name)

ThrowHelper.ThrowArgumentExceptionForsBitwiseEqualTo(value, target, name);
}
else if (typeof(T) == typeof(ulong) ||
typeof(T) == typeof(long) ||
typeof(T) == typeof(double))
else if (sizeof(T) == 8)
{
ulong valueULong = Unsafe.As<T, ulong>(ref value);
ulong targetULong = Unsafe.As<T, ulong>(ref target);

if (valueULong == targetULong)
if (Bit64Compare(ref valueULong, ref targetULong))
{
return;
}

ThrowHelper.ThrowArgumentExceptionForsBitwiseEqualTo(value, target, name);
}
else
else if (sizeof(T) == 16)
{
ref byte valueRef = ref Unsafe.As<T, byte>(ref value);
ref byte targetRef = ref Unsafe.As<T, byte>(ref target);
int bytesCount = Unsafe.SizeOf<T>();
ulong valueULong0 = Unsafe.As<T, ulong>(ref value);
ulong targetULong0 = Unsafe.As<T, ulong>(ref target);

for (int i = 0; i < bytesCount; i++)
if (Bit64Compare(ref valueULong0, ref targetULong0))
{
byte valueByte = Unsafe.Add(ref valueRef, i);
byte targetByte = Unsafe.Add(ref targetRef, i);
ulong valueULong1 = Unsafe.Add(ref Unsafe.As<T, ulong>(ref value), 1);
ulong targetULong1 = Unsafe.Add(ref Unsafe.As<T, ulong>(ref target), 1);

if (valueByte != targetByte)
if (Bit64Compare(ref valueULong1, ref targetULong1))
{
ThrowHelper.ThrowArgumentExceptionForsBitwiseEqualTo(value, target, name);
return;
}
}

ThrowHelper.ThrowArgumentExceptionForsBitwiseEqualTo(value, target, name);
}
else
{
Span<byte> valueBytes = new Span<byte>(Unsafe.AsPointer(ref value), sizeof(T));
Span<byte> targetBytes = new Span<byte>(Unsafe.AsPointer(ref target), sizeof(T));

if (valueBytes.SequenceEqual(targetBytes))
{
return;
}

ThrowHelper.ThrowArgumentExceptionForsBitwiseEqualTo(value, target, name);
}
}

// Compares 64 bits of data from two given memory locations for bitwise equality
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe bool Bit64Compare(ref ulong left, ref ulong right)
{
// Handles 32 bit case, because using ulong is inefficient
if (sizeof(IntPtr) == 4)
{
ref int r0 = ref Unsafe.As<ulong, int>(ref left);
ref int r1 = ref Unsafe.As<ulong, int>(ref right);

return r0 == r1 &&
Unsafe.Add(ref r0, 1) == Unsafe.Add(ref r1, 1);
}

return left == right;
}

/// <summary>
Expand Down
52 changes: 46 additions & 6 deletions UnitTests/UnitTests.Shared/Diagnostics/Test_Guard.cs
Expand Up @@ -156,25 +156,65 @@ public void Test_Guard_IsNotEqualTo_Fail()
public void Test_Guard_IsBitwiseEqualTo_Ok()
{
Guard.IsBitwiseEqualTo(byte.MaxValue, byte.MaxValue, nameof(Test_Guard_IsBitwiseEqualTo_Ok));
Guard.IsBitwiseEqualTo(DateTime.MaxValue, DateTime.MaxValue, nameof(Test_Guard_IsBitwiseEqualTo_Ok));
Guard.IsBitwiseEqualTo(double.Epsilon, double.Epsilon, nameof(Test_Guard_IsBitwiseEqualTo_Ok));
Guard.IsBitwiseEqualTo(MathF.PI, MathF.PI, nameof(Test_Guard_IsBitwiseEqualTo_Ok));
Guard.IsBitwiseEqualTo(double.Epsilon, double.Epsilon, nameof(Test_Guard_IsBitwiseEqualTo_Ok));

var guid = Guid.NewGuid();
Guard.IsBitwiseEqualTo(guid, guid, nameof(Test_Guard_IsBitwiseEqualTo_Ok));

// tests the >16 byte case where the loop is called
var biggerThanLimit = new BiggerThanLimit(0, 3, ulong.MaxValue, ulong.MinValue);
Guard.IsBitwiseEqualTo(biggerThanLimit, biggerThanLimit, nameof(Test_Guard_IsBitwiseEqualTo_Ok));
}

[TestCategory("Guard")]
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Test_Guard_IsBitwiseEqualTo_Size8Fail()
{
Guard.IsBitwiseEqualTo(double.PositiveInfinity, double.Epsilon, nameof(Test_Guard_IsBitwiseEqualTo_Size8Fail));
Guard.IsBitwiseEqualTo(DateTime.Now, DateTime.Today, nameof(Test_Guard_IsBitwiseEqualTo_Size8Fail));
}

[TestCategory("Guard")]
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Test_Guard_IsBitwiseEqualTo_SingleFail()
public void Test_Guard_IsBitwiseEqualTo_Size16Fail()
{
Guard.IsBitwiseEqualTo(double.PositiveInfinity, double.Epsilon, nameof(Test_Guard_IsBitwiseEqualTo_SingleFail));
Guard.IsBitwiseEqualTo(decimal.MaxValue, decimal.MinusOne, nameof(Test_Guard_IsBitwiseEqualTo_Size16Fail));
Guard.IsBitwiseEqualTo(Guid.NewGuid(), Guid.NewGuid(), nameof(Test_Guard_IsBitwiseEqualTo_Size16Fail));
}

// a >16 byte struct for testing IsBitwiseEqual's pathway for >16 byte types
private struct BiggerThanLimit
{
public BiggerThanLimit(ulong a, ulong b, ulong c, ulong d)
{
A = a;
B = b;
C = c;
D = d;
}

public ulong A;

public ulong B;

public ulong C;

public ulong D;
}

[TestCategory("Guard")]
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Test_Guard_IsBitwiseEqualTo_LoopFail()
public void Test_Guard_IsBitwiseEqualTo_SequenceEqualFail()
{
Guard.IsBitwiseEqualTo(DateTime.Now, DateTime.Today, nameof(Test_Guard_IsBitwiseEqualTo_LoopFail));
// tests the >16 byte case where the loop is called
var biggerThanLimit0 = new BiggerThanLimit(0, 3, ulong.MaxValue, ulong.MinValue);
var biggerThanLimit1 = new BiggerThanLimit(long.MaxValue + 1UL, 99, ulong.MaxValue ^ 0xF7UL, ulong.MinValue ^ 5555UL);

Guard.IsBitwiseEqualTo(biggerThanLimit0, biggerThanLimit1, nameof(Test_Guard_IsBitwiseEqualTo_SequenceEqualFail));
}

[TestCategory("Guard")]
Expand Down