// 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; } }