-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
TitleHHHH
authored and
TitleHHHH
committed
Sep 28, 2024
1 parent
9eeb172
commit d572a00
Showing
5 changed files
with
274 additions
and
4 deletions.
There are no files selected for viewing
205 changes: 205 additions & 0 deletions
205
src/McProtoNet/McProtoNet.Benchmark/BinaryPrimitivesTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
using System; | ||
using System.Buffers.Binary; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
using System.Runtime.Intrinsics; | ||
|
||
namespace McProtoNet.Benchmark; | ||
|
||
public static class BinaryPrimitivesTest | ||
{ | ||
|
||
[CLSCompliant(false)] | ||
public static void ReverseEndianness(ReadOnlySpan<ushort> source, Span<ushort> destination) => | ||
ReverseEndianness<short, Int16EndiannessReverser>(MemoryMarshal.Cast<ushort, short>(source), | ||
MemoryMarshal.Cast<ushort, short>(destination)); | ||
|
||
/// <inheritdoc cref="ReverseEndianness(ReadOnlySpan{ushort}, Span{ushort})" /> | ||
public static void ReverseEndianness(ReadOnlySpan<short> source, Span<short> destination) => | ||
ReverseEndianness<short, Int16EndiannessReverser>(source, destination); | ||
|
||
/// <inheritdoc cref="ReverseEndianness(ReadOnlySpan{ushort}, Span{ushort})" /> | ||
[CLSCompliant(false)] | ||
public static void ReverseEndianness(ReadOnlySpan<uint> source, Span<uint> destination) => | ||
ReverseEndianness<int, Int32EndiannessReverser>(MemoryMarshal.Cast<uint, int>(source), | ||
MemoryMarshal.Cast<uint, int>(destination)); | ||
|
||
/// <inheritdoc cref="ReverseEndianness(ReadOnlySpan{ushort}, Span{ushort})" /> | ||
public static void ReverseEndianness(ReadOnlySpan<int> source, Span<int> destination) => | ||
ReverseEndianness<int, Int32EndiannessReverser>(source, destination); | ||
|
||
/// <inheritdoc cref="ReverseEndianness(ReadOnlySpan{ushort}, Span{ushort})" /> | ||
[CLSCompliant(false)] | ||
public static void ReverseEndianness(ReadOnlySpan<ulong> source, Span<ulong> destination) => | ||
ReverseEndianness<long, Int64EndiannessReverser>(MemoryMarshal.Cast<ulong, long>(source), | ||
MemoryMarshal.Cast<ulong, long>(destination)); | ||
|
||
/// <inheritdoc cref="ReverseEndianness(ReadOnlySpan{ushort}, Span{ushort})" /> | ||
public static void ReverseEndianness(ReadOnlySpan<long> source, Span<long> destination) => | ||
ReverseEndianness<long, Int64EndiannessReverser>(source, destination); | ||
|
||
/// <inheritdoc cref="ReverseEndianness(ReadOnlySpan{ushort}, Span{ushort})" /> | ||
[CLSCompliant(false)] | ||
public static void ReverseEndianness(ReadOnlySpan<nuint> source, Span<nuint> destination) => | ||
#if TARGET_64BIT | ||
ReverseEndianness<long, Int64EndiannessReverser>(MemoryMarshal.Cast<nuint, long>(source), MemoryMarshal.Cast<nuint, long>(destination)); | ||
#else | ||
ReverseEndianness<int, Int32EndiannessReverser>(MemoryMarshal.Cast<nuint, int>(source), | ||
MemoryMarshal.Cast<nuint, int>(destination)); | ||
#endif | ||
|
||
/// <inheritdoc cref="ReverseEndianness(ReadOnlySpan{ushort}, Span{ushort})" /> | ||
public static void ReverseEndianness(ReadOnlySpan<nint> source, Span<nint> destination) => | ||
#if TARGET_64BIT | ||
ReverseEndianness<long, Int64EndiannessReverser>(MemoryMarshal.Cast<nint, long>(source), MemoryMarshal.Cast<nint, long>(destination)); | ||
#else | ||
ReverseEndianness<int, Int32EndiannessReverser>(MemoryMarshal.Cast<nint, int>(source), | ||
MemoryMarshal.Cast<nint, int>(destination)); | ||
#endif | ||
|
||
private readonly struct Int16EndiannessReverser : IEndiannessReverser<short> | ||
{ | ||
public static short Reverse(short value) => | ||
BinaryPrimitives.ReverseEndianness(value); | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static Vector128<short> Reverse(Vector128<short> vector) => | ||
Vector128.ShiftLeft(vector, 8) | Vector128.ShiftRightLogical(vector, 8); | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static Vector256<short> Reverse(Vector256<short> vector) => | ||
Vector256.ShiftLeft(vector, 8) | Vector256.ShiftRightLogical(vector, 8); | ||
} | ||
|
||
private readonly struct Int32EndiannessReverser : IEndiannessReverser<int> | ||
{ | ||
public static int Reverse(int value) => | ||
BinaryPrimitives.ReverseEndianness(value); | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static Vector128<int> Reverse(Vector128<int> vector) => | ||
Impl.ShuffleUnsafe(vector.AsByte(), | ||
Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12)).AsInt32(); | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static Vector256<int> Reverse(Vector256<int> vector) => | ||
Impl.ShuffleUnsafe(vector.AsByte(), | ||
Vector256.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12, 19, 18, 17, 16, 23, 22, 21, | ||
20, 27, 26, 25, 24, 31, 30, 29, 28)).AsInt32(); | ||
} | ||
|
||
private readonly struct Int64EndiannessReverser : IEndiannessReverser<long> | ||
{ | ||
public static long Reverse(long value) => | ||
BinaryPrimitives.ReverseEndianness(value); | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static Vector128<long> Reverse(Vector128<long> vector) => | ||
Impl.ShuffleUnsafe(vector.AsByte(), | ||
Vector128.Create((byte)7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8)) | ||
.AsInt64(); | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static Vector256<long> Reverse(Vector256<long> vector) => | ||
Impl.ShuffleUnsafe(vector.AsByte(), | ||
Vector256.Create((byte)7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 23, 22, 21, 20, 19, 18, 17, | ||
16, 31, 30, 29, 28, 27, 26, 25, 24)).AsInt64(); | ||
} | ||
|
||
private static void ReverseEndianness<T, TReverser>(ReadOnlySpan<T> source, Span<T> destination) | ||
where T : struct | ||
where TReverser : IEndiannessReverser<T> | ||
{ | ||
if (destination.Length < source.Length) | ||
{ | ||
//ThrowDestinationTooSmall(); | ||
throw new Exception("destination is small"); | ||
} | ||
|
||
ref T sourceRef = ref MemoryMarshal.GetReference(source); | ||
ref T destRef = ref MemoryMarshal.GetReference(destination); | ||
|
||
if (Unsafe.AreSame(ref sourceRef, ref destRef) || | ||
!source.Overlaps(destination, out int elementOffset) || | ||
elementOffset < 0) | ||
{ | ||
// Either there's no overlap between the source and the destination, or there's overlap but the | ||
// destination starts at or before the source. That means we can safely iterate from beginning | ||
// to end of the source and not have to worry about writing into the destination and clobbering | ||
// source data we haven't yet read. | ||
|
||
int i = 0; | ||
|
||
if (Vector256.IsHardwareAccelerated) | ||
{ | ||
while (i <= source.Length - Vector256<T>.Count) | ||
{ | ||
Vector256.StoreUnsafe(TReverser.Reverse(Vector256.LoadUnsafe(ref sourceRef, (uint)i)), ref destRef, | ||
(uint)i); | ||
i += Vector256<T>.Count; | ||
} | ||
} | ||
|
||
if (Vector128.IsHardwareAccelerated) | ||
{ | ||
while (i <= source.Length - Vector128<T>.Count) | ||
{ | ||
Vector128.StoreUnsafe(TReverser.Reverse(Vector128.LoadUnsafe(ref sourceRef, (uint)i)), ref destRef, | ||
(uint)i); | ||
i += Vector128<T>.Count; | ||
} | ||
} | ||
|
||
while (i < source.Length) | ||
{ | ||
Unsafe.Add(ref destRef, i) = TReverser.Reverse(Unsafe.Add(ref sourceRef, i)); | ||
i++; | ||
} | ||
} | ||
else | ||
{ | ||
// There's overlap between the source and the destination, and the source starts before the destination. | ||
// That means if we were to iterate from beginning to end, reading from the source and writing to the | ||
// destination, we'd overwrite source elements not yet read. To avoid that, we iterate from end to beginning. | ||
|
||
int i = source.Length; | ||
|
||
if (Vector256.IsHardwareAccelerated) | ||
{ | ||
while (i >= Vector256<T>.Count) | ||
{ | ||
i -= Vector256<T>.Count; | ||
Vector256.StoreUnsafe(TReverser.Reverse(Vector256.LoadUnsafe(ref sourceRef, (uint)i)), ref destRef, | ||
(uint)i); | ||
} | ||
} | ||
|
||
if (Vector128.IsHardwareAccelerated) | ||
{ | ||
while (i >= Vector128<T>.Count) | ||
{ | ||
i -= Vector128<T>.Count; | ||
Vector128.StoreUnsafe(TReverser.Reverse(Vector128.LoadUnsafe(ref sourceRef, (uint)i)), ref destRef, | ||
(uint)i); | ||
} | ||
} | ||
|
||
while (i > 0) | ||
{ | ||
i--; | ||
Unsafe.Add(ref destRef, i) = TReverser.Reverse(Unsafe.Add(ref sourceRef, i)); | ||
} | ||
} | ||
} | ||
|
||
private interface IEndiannessReverser<T> where T : struct | ||
{ | ||
static abstract T Reverse(T value); | ||
static abstract Vector128<T> Reverse(Vector128<T> vector); | ||
static abstract Vector256<T> Reverse(Vector256<T> vector); | ||
} | ||
|
||
|
||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using System.Runtime.Intrinsics; | ||
using System.Runtime.Intrinsics.X86; | ||
|
||
namespace McProtoNet.Benchmark; | ||
|
||
public static class Impl | ||
{ | ||
public static Vector128<byte> ShuffleUnsafe(Vector128<byte> values, Vector128<byte> indices) | ||
{ | ||
if (Ssse3.IsSupported) return Ssse3.Shuffle(values, indices); | ||
return Vector128.Shuffle(values, indices); | ||
} | ||
|
||
public static Vector256<byte> ShuffleUnsafe(this Vector256<byte> values, Vector256<byte> indices) | ||
{ | ||
if (Avx2.IsSupported) | ||
{ | ||
var indicesXord = Avx2.And(Avx2.Xor(indices, Vector256.Create(Vector128.Create((byte)0), Vector128.Create((byte)0x10))), Vector256.Create((byte)0x9F)); | ||
var swap = Avx2.Permute2x128(values, values, 0b00000001); | ||
var shuf1 = Avx2.Shuffle(values, indices); | ||
var shuf2 = Avx2.Shuffle(swap, indices); | ||
var selection = Avx2.CompareGreaterThan(indicesXord.AsSByte(), Vector256.Create((sbyte)0x0F)).AsByte(); | ||
return Avx2.BlendVariable(shuf1, shuf2, selection); | ||
} | ||
return Vector256.Shuffle(values, indices); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters