Skip to content

Commit

Permalink
benchs
Browse files Browse the repository at this point in the history
  • Loading branch information
TitleHHHH authored and TitleHHHH committed Sep 28, 2024
1 parent 9eeb172 commit d572a00
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 4 deletions.
205 changes: 205 additions & 0 deletions src/McProtoNet/McProtoNet.Benchmark/BinaryPrimitivesTest.cs
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);
}




}
28 changes: 28 additions & 0 deletions src/McProtoNet/McProtoNet.Benchmark/Impl.cs
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);
}

}
2 changes: 1 addition & 1 deletion src/McProtoNet/McProtoNet.Benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
using System.Threading.Tasks;
using BenchmarkDotNet.Running;
Expand Down
41 changes: 38 additions & 3 deletions src/McProtoNet/McProtoNet.Benchmark/ReadBigEndianBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Buffers.Binary;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using BenchmarkDotNet.Attributes;
using DotNext.Buffers;

Expand All @@ -13,12 +14,19 @@ public class ReadBigEndianBenchmarks
public byte[] TestArr;
private Random r = new();
[Params(10, 100, 100000)] public int Count { get; set; }

//public int Count = 10;
[GlobalSetup]
public void Setup()
{
TestArr = new byte[sizeof(long) * Count];

Random r = new(71);
r.NextBytes(MemoryMarshal.AsBytes(new Span<Vector128<byte>>(ref A128)));
r.NextBytes(MemoryMarshal.AsBytes(new Span<Vector128<byte>>(ref B128)));
r.NextBytes(MemoryMarshal.AsBytes(new Span<Vector256<byte>>(ref A256)));
r.NextBytes(MemoryMarshal.AsBytes(new Span<Vector256<byte>>(ref B256)));
// int Count = 10;
TestArr = new byte[sizeof(long) * Count];

scoped SpanWriter<byte> writer = new SpanWriter<byte>(TestArr);
for (int i = 0; i < Count; i++)
{
Expand All @@ -28,7 +36,7 @@ public void Setup()
}


[Benchmark]
[Benchmark]
public long[] SpanReader()
{
scoped SpanReader<byte> reader = new SpanReader<byte>(TestArr);
Expand All @@ -50,4 +58,31 @@ public long[] SimdRead()
BinaryPrimitives.ReverseEndianness(numbers, source);
return source;
}

[Benchmark]
public long[] SimdReadUnsafe()
{
Span<long> numbers = MemoryMarshal.Cast<byte, long>(TestArr);
long[] source = new long[Count];
if (BitConverter.IsLittleEndian)
BinaryPrimitivesTest.ReverseEndianness(numbers, source);
return source;
}
public Vector128<byte> A128;
public Vector128<byte> B128;

public Vector256<byte> A256;
public Vector256<byte> B256;
//[Benchmark]
public Vector128<byte> Shuffle128()
{
return Vector128.Shuffle(A128, B128);
}
//[Benchmark]
public Vector128<byte> ShuffleUnsafe128()
{
return Impl.ShuffleUnsafe(A128, B128);
}


}
2 changes: 2 additions & 0 deletions src/Sandbox/SandBoxMcProtoNet/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Reactive.Linq;
using System.Runtime.Intrinsics.X86;
using System.Text;
using DotNext.Collections.Generic;
using McProtoNet.Client;
Expand All @@ -17,6 +18,7 @@ public static async Task Main(string[] args)
Console.WriteLine("Start");
try
{

var list = new List<MinecraftClient>();
var listProtocols = new List<MultiProtocol>();
for (int i = 0; i < 200; i++)
Expand Down

0 comments on commit d572a00

Please sign in to comment.