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

Codegen improvements (removed unnecessary movsxd ops) #3378

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
08a5cb2
Improved codegen for ReadOnlySpanExtensions.DangerousGetLookupReferen…
Sergio0694 Jul 5, 2020
42dc9d8
Added explicit unchecked for clarity
Sergio0694 Jul 5, 2020
83effb9
Merge branch 'master' into optimization/movsxd-removal
Sergio0694 Jul 10, 2020
4ad5f6a
Merge branch 'master' into optimization/movsxd-removal
Sergio0694 Jul 12, 2020
7069e73
Removed movsxd from DangerousGetReferenceAt APIs
Sergio0694 Jul 12, 2020
d18566c
Removed movsxd from ParallelHelper iterators
Sergio0694 Jul 12, 2020
315f687
Removed movsxd from Count/Djb2 extensions
Sergio0694 Jul 14, 2020
4ebf455
Added explicit #if for using statement for clarity
Sergio0694 Jul 14, 2020
08c859b
Further codegen improvements (especially on 32 bit)
Sergio0694 Jul 14, 2020
929e4d6
Merge branch 'master' into optimization/movsxd-removal
Sergio0694 Jul 21, 2020
f5e6fdf
Merge branch 'master' into optimization/movsxd-removal
Sergio0694 Jul 31, 2020
f643026
Merge branch 'master' into optimization/movsxd-removal
Sergio0694 Aug 8, 2020
00d9681
Merge branch 'master' into optimization/movsxd-removal
Sergio0694 Aug 12, 2020
a99d056
Merge branch 'master' into optimization/movsxd-removal
Sergio0694 Sep 24, 2020
4041b8a
Merge branch 'master' into optimization/movsxd-removal
Sergio0694 Sep 25, 2020
da68b72
Added comment to explain the (IntPtr)(void*)(uint) casts
Sergio0694 Oct 6, 2020
51e44f7
Merge branch 'master' into optimization/movsxd-removal
Sergio0694 Oct 8, 2020
aef54c6
Added StringPool info in package description
Sergio0694 Oct 8, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ public static ref T DangerousGetReference<T>(this T[,] array)
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
public static unsafe ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray2DData>(array);
int offset = (i * arrayData.Width) + j;
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
ref T ri = ref Unsafe.Add(ref r0, offset);
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset);

return ref ri;
#else
Expand All @@ -82,10 +82,7 @@ public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
return ref array[i, j];
}

unsafe
{
return ref Unsafe.AsRef<T>(null);
}
return ref Unsafe.AsRef<T>(null);
#endif
}

Expand Down Expand Up @@ -274,11 +271,11 @@ public static Span<T> AsSpan<T>(this T[,] array)
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count<T>(this T[,] array, T value)
public static unsafe int Count<T>(this T[,] array, T value)
where T : IEquatable<T>
{
ref T r0 = ref array.DangerousGetReference();
IntPtr length = (IntPtr)array.Length;
IntPtr length = (IntPtr)(void*)(uint)array.Length;

return SpanHelper.Count(ref r0, length, value);
}
Expand All @@ -293,11 +290,11 @@ public static int Count<T>(this T[,] array, T value)
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode<T>(this T[,] array)
public static unsafe int GetDjb2HashCode<T>(this T[,] array)
where T : notnull
{
ref T r0 = ref array.DangerousGetReference();
IntPtr length = (IntPtr)array.Length;
IntPtr length = (IntPtr)(void*)(uint)array.Length;

return SpanHelper.GetDjb2HashCode(ref r0, length);
}
Expand Down
17 changes: 7 additions & 10 deletions Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ public static ref T DangerousGetReference<T>(this T[] array)
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
public static unsafe ref T DangerousGetReferenceAt<T>(this T[] array, int i)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArrayData>(array);
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
ref T ri = ref Unsafe.Add(ref r0, i);
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);

return ref ri;
#else
Expand All @@ -76,10 +76,7 @@ public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
return ref array[i];
}

unsafe
{
return ref Unsafe.AsRef<T>(null);
}
return ref Unsafe.AsRef<T>(null);
#endif
}

Expand Down Expand Up @@ -114,11 +111,11 @@ private sealed class RawArrayData
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count<T>(this T[] array, T value)
public static unsafe int Count<T>(this T[] array, T value)
where T : IEquatable<T>
{
ref T r0 = ref array.DangerousGetReference();
IntPtr length = (IntPtr)array.Length;
IntPtr length = (IntPtr)(void*)(uint)array.Length;

return SpanHelper.Count(ref r0, length, value);
}
Expand Down Expand Up @@ -185,11 +182,11 @@ public static SpanTokenizer<T> Tokenize<T>(this T[] array, T separator)
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode<T>(this T[] array)
public static unsafe int GetDjb2HashCode<T>(this T[] array)
where T : notnull
{
ref T r0 = ref array.DangerousGetReference();
IntPtr length = (IntPtr)array.Length;
IntPtr length = (IntPtr)(void*)(uint)array.Length;

return SpanHelper.GetDjb2HashCode(ref r0, length);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,40 @@ public static ref T DangerousGetReference<T>(this ReadOnlySpan<T> span)
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
public static unsafe ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
{
// Here we assume the input index will never be negative, so we do an unsafe cast to
// force the JIT to skip the sign extension when going from int to native int.
// On .NET Core 3.1, if we only use Unsafe.Add(ref r0, i), we get the following:
// =============================
// L0000: mov rax, [rcx]
// L0003: movsxd rdx, edx
// L0006: lea rax, [rax+rdx*4]
// L000a: ret
// =============================
// Note the movsxd (move with sign extension) to expand the index passed in edx to
// the whole rdx register. This is unnecessary and more expensive than just a mov,
// which when done to a large register size automatically zeroes the upper bits.
// With the (IntPtr)(void*)(uint) cast, we get the following codegen instead:
// =============================
// L0000: mov rax, [rcx]
// L0003: mov edx, edx
// L0005: lea rax, [rax+rdx*4]
// L0009: ret
// =============================
// Here we can see how the index is extended to a native integer with just a mov,
// which effectively only zeroes the upper bits of the same register used as source.
// These three casts are a bit verbose, but they do the trick on both 32 bit and 64
// bit architectures, producing optimal code in both cases (they are either completely
// elided on 32 bit systems, or result in the correct register expansion when on 64 bit).
// We first do an unchecked conversion to uint (which is just a reinterpret-cast). We
// then cast to void*, which lets the following IntPtr cast avoid the range check on 32 bit
// (since uint could be out of range there if the original index was negative). The final
// result is a clean mov as shown above. This will eventually be natively supported by the
// JIT compiler (see https://github.com/dotnet/runtime/issues/38794), but doing this here
// still ensures the optimal codegen even on existing runtimes (eg. .NET Core 2.1 and 3.1).
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, i);
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);

return ref ri;
}
Expand Down Expand Up @@ -87,7 +117,7 @@ public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
public static unsafe ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
{
// Check whether the input is in range by first casting both
// operands to uint and then comparing them, as this allows
Expand All @@ -106,12 +136,12 @@ public static ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<
// lookup table can just be assumed to always be false.
bool isInRange = (uint)i < (uint)span.Length;
byte rangeFlag = Unsafe.As<bool, byte>(ref isInRange);
int
negativeFlag = rangeFlag - 1,
uint
negativeFlag = unchecked(rangeFlag - 1u),
mask = ~negativeFlag,
offset = i & mask;
offset = (uint)i & mask;
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T r1 = ref Unsafe.Add(ref r0, offset);
ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)offset);

return ref r1;
}
Expand Down Expand Up @@ -165,11 +195,11 @@ public static unsafe int IndexOf<T>(this ReadOnlySpan<T> span, in T value)
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count<T>(this ReadOnlySpan<T> span, T value)
public static unsafe int Count<T>(this ReadOnlySpan<T> span, T value)
where T : IEquatable<T>
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr length = (IntPtr)span.Length;
IntPtr length = (IntPtr)(void*)(uint)span.Length;

return SpanHelper.Count(ref r0, length, value);
}
Expand Down Expand Up @@ -291,11 +321,11 @@ public static ReadOnlySpanTokenizer<T> Tokenize<T>(this ReadOnlySpan<T> span, T
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
public static unsafe int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
where T : notnull
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr length = (IntPtr)span.Length;
IntPtr length = (IntPtr)(void*)(uint)span.Length;

return SpanHelper.GetDjb2HashCode(ref r0, length);
}
Expand Down
12 changes: 6 additions & 6 deletions Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ public static ref T DangerousGetReference<T>(this Span<T> span)
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
public static unsafe ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, i);
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);

return ref ri;
}
Expand Down Expand Up @@ -140,11 +140,11 @@ public static unsafe int IndexOf<T>(this Span<T> span, ref T value)
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count<T>(this Span<T> span, T value)
public static unsafe int Count<T>(this Span<T> span, T value)
where T : IEquatable<T>
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr length = (IntPtr)span.Length;
IntPtr length = (IntPtr)(void*)(uint)span.Length;

return SpanHelper.Count(ref r0, length, value);
}
Expand Down Expand Up @@ -211,11 +211,11 @@ public static SpanTokenizer<T> Tokenize<T>(this Span<T> span, T separator)
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode<T>(this Span<T> span)
public static unsafe int GetDjb2HashCode<T>(this Span<T> span)
where T : notnull
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr length = (IntPtr)span.Length;
IntPtr length = (IntPtr)(void*)(uint)span.Length;

return SpanHelper.GetDjb2HashCode(ref r0, length);
}
Expand Down
12 changes: 6 additions & 6 deletions Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static ref char DangerousGetReference(this string text)
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref char DangerousGetReferenceAt(this string text, int i)
public static unsafe ref char DangerousGetReferenceAt(this string text, int i)
{
#if NETCOREAPP3_1
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
Expand All @@ -57,7 +57,7 @@ public static ref char DangerousGetReferenceAt(this string text, int i)
#else
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
#endif
ref char ri = ref Unsafe.Add(ref r0, i);
ref char ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);

return ref ri;
}
Expand Down Expand Up @@ -91,10 +91,10 @@ private sealed class RawStringData
/// <returns>The number of occurrences of <paramref name="c"/> in <paramref name="text"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count(this string text, char c)
public static unsafe int Count(this string text, char c)
{
ref char r0 = ref text.DangerousGetReference();
IntPtr length = (IntPtr)text.Length;
IntPtr length = (IntPtr)(void*)(uint)text.Length;

return SpanHelper.Count(ref r0, length, c);
}
Expand Down Expand Up @@ -157,10 +157,10 @@ public static ReadOnlySpanTokenizer<char> Tokenize(this string text, char separa
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode(this string text)
public static unsafe int GetDjb2HashCode(this string text)
{
ref char r0 = ref text.DangerousGetReference();
IntPtr length = (IntPtr)text.Length;
IntPtr length = (IntPtr)(void*)(uint)text.Length;

return SpanHelper.GetDjb2HashCode(ref r0, length);
}
Expand Down
14 changes: 10 additions & 4 deletions Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static int Combine(ReadOnlySpan<T> span)
/// <remarks>The returned hash code is not processed through <see cref="HashCode"/> APIs.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int CombineValues(ReadOnlySpan<T> span)
internal static unsafe int CombineValues(ReadOnlySpan<T> span)
{
ref T r0 = ref MemoryMarshal.GetReference(span);

Expand All @@ -67,13 +67,19 @@ internal static int CombineValues(ReadOnlySpan<T> span)
// compiler, so this branch will never actually be executed by the code.
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)span.Length);
return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)(void*)(uint)span.Length);
}
#endif

// Get the info for the target memory area to process
// Get the info for the target memory area to process.
// The line below is computing the total byte size for the span,
// and we cast both input factors to uint first to avoid sign extensions
// (they're both guaranteed to always be positive values), and to let the
// JIT avoid the 64 bit computation entirely when running in a 32 bit
// process. In that case it will just compute the byte size as a 32 bit
// multiplication with overflow, which is guaranteed never to happen anyway.
ref byte rb = ref Unsafe.As<T, byte>(ref r0);
IntPtr length = (IntPtr)((long)span.Length * Unsafe.SizeOf<T>());
IntPtr length = (IntPtr)(void*)((uint)span.Length * (uint)Unsafe.SizeOf<T>());

return SpanHelper.GetDjb2LikeByteHash(ref rb, length);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public InActionInvoker(
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
public unsafe void Invoke(int i)
{
int
low = i * this.batchSize,
Expand All @@ -147,7 +147,7 @@ public void Invoke(int i)

for (int j = low; j < end; j++)
{
ref TItem rj = ref Unsafe.Add(ref r0, j);
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);

Unsafe.AsRef(this.action).Invoke(rj);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public RefActionInvoker(
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
public unsafe void Invoke(int i)
{
int
low = i * this.batchSize,
Expand All @@ -147,7 +147,7 @@ public void Invoke(int i)

for (int j = low; j < end; j++)
{
ref TItem rj = ref Unsafe.Add(ref r0, j);
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);

Unsafe.AsRef(this.action).Invoke(ref rj);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- MemoryBufferWriter&lt;T&gt;: an IBufferWriter&lt;T&gt;: implementation that can wrap external Memory&lt;T&gt;: instances.
- MemoryOwner&lt;T&gt;: an IMemoryOwner&lt;T&gt; implementation with an embedded length and a fast Span&lt;T&gt; accessor.
- SpanOwner&lt;T&gt;: a stack-only type with the ability to rent a buffer of a specified length and getting a Span&lt;T&gt; from it.
- StringPool: a configurable pool for string instances that be used to minimize allocations when creating multiple strings from char buffers.
- String, array, Span&lt;T&gt;, Memory&lt;T&gt; extensions and more, all focused on high performance.
- HashCode&lt;T&gt;: a SIMD-enabled extension of HashCode to quickly process sequences of values.
- BitHelper: a class with helper methods to perform bit operations on numeric types.
Expand Down