Skip to content

Commit 8b4af58

Browse files
committed
Use Unsafe.SizeOf instead of manually calculating struct size.
1 parent 61c6342 commit 8b4af58

File tree

3 files changed

+17
-65
lines changed

3 files changed

+17
-65
lines changed

src/BenchmarkDotNet/Code/DeclarationsProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ internal class NonVoidDeclarationsProvider : DeclarationsProvider
8585

8686
public NonVoidDeclarationsProvider(BenchmarkCase benchmark) : base(benchmark)
8787
{
88-
overheadReturnsDefault = WorkloadMethodReturnType.IsByRefLike() || WorkloadMethodReturnType.IsDefaultFasterThanField(Benchmark.GetRuntime().RuntimeMoniker == Jobs.RuntimeMoniker.Mono);
88+
overheadReturnsDefault = WorkloadMethodReturnType.IsDefaultFasterThanField(Benchmark.GetRuntime().RuntimeMoniker == Jobs.RuntimeMoniker.Mono);
8989
}
9090

9191
public override string ConsumeField

src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs

+15-63
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
5+
using System.Runtime.CompilerServices;
56
using System.Runtime.InteropServices;
67
using BenchmarkDotNet.Attributes;
78

@@ -219,74 +220,25 @@ internal static bool IsByRefLike(this Type type)
219220
private const int MonoDefaultCutoffSize = 64;
220221

221222
// We use the fastest possible method to return a value of the workload return type in order to prevent the overhead method from taking longer than the workload method.
222-
// Classic Mono runs `default` slower than reading a field for very large structs. `default` is faster for all types in all other runtimes.
223223
internal static bool IsDefaultFasterThanField(this Type type, bool isClassicMono)
224-
=> !isClassicMono || type.SizeOfDefault() <= MonoDefaultCutoffSize;
225-
226-
private static int SizeOfDefault(this Type type) => type switch
224+
// Classic Mono runs `default` slower than reading a field for very large structs. `default` is faster for all types in all other runtimes.
225+
=> !isClassicMono
226+
// ByRefLike and pointer cannot be used as generic arguments, so check for them before getting the size.
227+
|| type.IsByRefLike() || type.IsPointer
228+
// We don't need to check the size for primitives and reference types.
229+
|| type.IsPrimitive || type.IsEnum || !type.IsValueType
230+
|| SizeOf(type) <= MonoDefaultCutoffSize;
231+
232+
private static int SizeOf(Type type)
227233
{
228-
_ when type == typeof(byte) || type == typeof(sbyte)
229-
=> 1,
230-
231-
_ when type == typeof(short) || type == typeof(ushort) || type == typeof(char)
232-
=> 2,
233-
234-
_ when type == typeof(int) || type == typeof(uint)
235-
=> 4,
236-
237-
_ when type == typeof(long) || type == typeof(ulong)
238-
=> 8,
239-
240-
_ when type.IsPointer || type.IsClass || type.IsInterface || type == typeof(IntPtr) || type == typeof(UIntPtr)
241-
=> IntPtr.Size,
242-
243-
_ when type.IsEnum
244-
=> Enum.GetUnderlyingType(type).SizeOfDefault(),
245-
246-
_ when type.IsValueType
247-
=> type.SizeOfDefaultStruct(),
248-
249-
_ => throw new Exception("Unknown type size: " + type.FullName)
250-
};
251-
252-
private static int SizeOfDefaultStruct(this Type structType)
253-
{
254-
// Find the offset of the highest field, so we only have to calculate the size of it + its offset.
255-
int fieldOffset = int.MinValue;
256-
FieldInfo maxField = null;
257-
bool containsReference = false;
258-
foreach (var field in structType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
259-
{
260-
var fieldType = field.FieldType;
261-
containsReference |= fieldType.IsPointer || fieldType.IsClass || fieldType.IsInterface;
262-
int offset = field.GetFieldOffset();
263-
if (offset > fieldOffset)
264-
{
265-
fieldOffset = offset;
266-
maxField = field;
267-
}
268-
}
269-
if (maxField == null)
270-
{
271-
// Runtime enforces minimum struct size as 1 byte.
272-
return 1;
273-
}
274-
// Runtime pads struct for alignment purposes if it contains a reference.
275-
int structSize = maxField.FieldType.SizeOfDefault() + fieldOffset;
276-
return containsReference
277-
? GetPaddedStructSize(structSize)
278-
: structSize;
234+
return (int) GetGenericSizeOfMethod(type).Invoke(null, null);
279235
}
280236

281-
// Code obtained from https://stackoverflow.com/a/56512720
282-
private static int GetFieldOffset(this FieldInfo fi) => Marshal.ReadInt32(fi.FieldHandle.Value + (4 + IntPtr.Size)) & 0xFFFFFF;
283-
284-
private static int GetPaddedStructSize(int fieldsSize)
237+
private static MethodInfo GetGenericSizeOfMethod(Type type)
285238
{
286-
Math.DivRem(fieldsSize, IntPtr.Size, out int padding);
287-
return padding == 0
288-
? fieldsSize
289-
: fieldsSize - padding + IntPtr.Size;
239+
return typeof(Unsafe).GetMethods(BindingFlags.Static | BindingFlags.Public)
240+
.Single(m => m.Name == nameof(Unsafe.SizeOf) && m.IsGenericMethodDefinition && m.ReturnType == typeof(int) && m.GetParameters().Length == 0)
241+
.MakeGenericMethod(type);
290242
}
291243
}
292244
}

src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumableConsumeEmitter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ internal class ConsumableConsumeEmitter : ConsumeEmitter
1919

2020
public ConsumableConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo)
2121
{
22-
overheadReturnsDefault = consumableTypeInfo.WorkloadMethodReturnType.IsByRefLike() || consumableTypeInfo.WorkloadMethodReturnType.IsDefaultFasterThanField(RuntimeInformation.IsOldMono);
22+
overheadReturnsDefault = consumableTypeInfo.WorkloadMethodReturnType.IsDefaultFasterThanField(RuntimeInformation.IsOldMono);
2323
}
2424

2525
protected override void OnDefineFieldsOverride(TypeBuilder runnableBuilder)

0 commit comments

Comments
 (0)