diff --git a/Benchmarks/MemCasting.cs b/Benchmarks/MemCasting.cs new file mode 100644 index 0000000..85d724e --- /dev/null +++ b/Benchmarks/MemCasting.cs @@ -0,0 +1,178 @@ +// Licensed to Timothy Schenk under the GNU AGPL Version 3 License. + +using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +namespace Benchmarks; + +[Config(typeof(GenericConfig))] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +public class MemCasting +{ + private byte[] _data = null!; + private byte[] _arrayData = null!; + + [Params(1000, 100000, 1000000)] public int N; + [GlobalSetup] + public void Setup() + { + _data = new byte[64]; + _arrayData = new byte[N * 64]; + Random.Shared.NextBytes(_data); + Random.Shared.NextBytes(_arrayData); + } + private static ElementalStats ReadElementalStatsMemoryMarshal(ref Span data) + { + return MemoryMarshal.Cast(data.Slice(0, 64))[0]; + } + private static ElementalStats ReadElementalStatsMemoryMarshal2(ref Span data) + { + return MemoryMarshal.Read(data.Slice(0, 64)); + } + private static ElementalStats ReadElementalStatsNew(ref Span data) + { + return new ElementalStats + { + MinimumFireDamage = BitConverter.ToInt32(data.Slice(0, 4)), // 140 -> 144 + MinimumWaterDamage = BitConverter.ToInt32(data.Slice(4, 4)), // 144 -> 148 + MinimumDarkDamage = BitConverter.ToInt32(data.Slice(8, 4)), // 148 -> 152 + MinimumHolyDamage = BitConverter.ToInt32(data.Slice(12, 4)), // 152 -> 156 + MaximumFireDamage = BitConverter.ToInt32(data.Slice(16, 4)), // 156 -> 160 + MaximumWaterDamage = BitConverter.ToInt32(data.Slice(20, 4)), // 160 -> 164 + MaximumDarkDamage = BitConverter.ToInt32(data.Slice(24, 4)), // 164 -> 168 + MaximumHolyDamage = BitConverter.ToInt32(data.Slice(28, 4)), // 168 -> 172 + ElementFire = BitConverter.ToUInt32(data.Slice(32, 4)), // 172 -> 176 + ElementWater = BitConverter.ToUInt32(data.Slice(36, 4)), // 176 -> 180 + ElementDark = BitConverter.ToUInt32(data.Slice(40, 4)), // 180 -> 184 + ElementHoly = BitConverter.ToUInt32(data.Slice(44, 4)), // 184 -> 188 + FireResistance = BitConverter.ToInt32(data.Slice(48, 4)), // 188 -> 192 + WaterResistance = BitConverter.ToInt32(data.Slice(52, 4)), // 192 -> 196 + DarkResistance = BitConverter.ToInt32(data.Slice(56, 4)), // 196 -> 200 + HolyResistance = BitConverter.ToInt32(data.Slice(60, 4)) // 200 -> 204 + }; + } + + [Benchmark] + public Span MemoryMarshalCastArray() + { + var data = _arrayData.AsSpan(); + var elements = MemoryMarshal.Cast(data); + + return elements; + } + + [Benchmark] + public Span MemoryMarshalReadArray() + { + Span statsArr = stackalloc ElementalStats[N + 1]; + var data = _data.AsSpan(); + for (int i = 0; i <= N; i++) + { + statsArr[i] = ReadElementalStatsMemoryMarshal2(ref data); + } + + return statsArr.ToArray(); + } + [Benchmark] + public Span ManualSpanSlicingArray() + { + Span statsArr = stackalloc ElementalStats[N + 1]; + var data = _data.AsSpan(); + for (int i = 0; i <= N; i++) + { + statsArr[i] = ReadElementalStatsNew(ref data); + } + + return statsArr.ToArray(); + } + + [Benchmark] + public ElementalStats MemoryMarshalCastSingle() + { + var data = _data.AsSpan(); + return ReadElementalStatsMemoryMarshal(ref data); + } + + [Benchmark] + public ElementalStats MemoryMarshalReadSingle() + { + var data = _data.AsSpan(); + return ReadElementalStatsMemoryMarshal2(ref data); + } + [Benchmark] + public ElementalStats ManualSpanSlicingSingle() + { + var data = _data.AsSpan(); + return ReadElementalStatsNew(ref data); + } + + [StructLayout(LayoutKind.Explicit, Size = 64)] + public struct ElementalStats + { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.I4)] + public int MinimumFireDamage; + + [FieldOffset(4)] + [MarshalAs(UnmanagedType.I4)] + public int MinimumWaterDamage; + + [FieldOffset(8)] + [MarshalAs(UnmanagedType.I4)] + public int MinimumDarkDamage; + + [FieldOffset(12)] + [MarshalAs(UnmanagedType.I4)] + public int MinimumHolyDamage; + + [FieldOffset(16)] + [MarshalAs(UnmanagedType.I4)] + public int MaximumFireDamage; + + [FieldOffset(20)] + [MarshalAs(UnmanagedType.I4)] + public int MaximumWaterDamage; + + [FieldOffset(24)] + [MarshalAs(UnmanagedType.I4)] + public int MaximumDarkDamage; + + [FieldOffset(28)] + [MarshalAs(UnmanagedType.I4)] + public int MaximumHolyDamage; + + [FieldOffset(32)] + [MarshalAs(UnmanagedType.U4)] + public uint ElementFire; + + [FieldOffset(36)] + [MarshalAs(UnmanagedType.U4)] + public uint ElementWater; + + [FieldOffset(40)] + [MarshalAs(UnmanagedType.U4)] + public uint ElementDark; + + [FieldOffset(44)] + [MarshalAs(UnmanagedType.U4)] + public uint ElementHoly; + + [FieldOffset(48)] + [MarshalAs(UnmanagedType.I4)] + public int FireResistance; + + [FieldOffset(52)] + [MarshalAs(UnmanagedType.I4)] + public int WaterResistance; + + [FieldOffset(56)] + [MarshalAs(UnmanagedType.I4)] + public int DarkResistance; + + [FieldOffset(60)] + [MarshalAs(UnmanagedType.I4)] + public int HolyResistance; + } + +} diff --git a/hex-patterns/basecharacterstatdata.hexproj b/hex-patterns/basecharacterstatdata.hexproj index 31093c0..41b4b68 100644 Binary files a/hex-patterns/basecharacterstatdata.hexproj and b/hex-patterns/basecharacterstatdata.hexproj differ