Skip to content

Commit cc207fa

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

File tree

3 files changed

+11
-63
lines changed

3 files changed

+11
-63
lines changed

src/BenchmarkDotNet/Code/DeclarationsProvider.cs

Lines changed: 1 addition & 1 deletion
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

Lines changed: 9 additions & 61 deletions
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

@@ -221,72 +222,19 @@ internal static bool IsByRefLike(this Type type)
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.
222223
// Classic Mono runs `default` slower than reading a field for very large structs. `default` is faster for all types in all other runtimes.
223224
internal static bool IsDefaultFasterThanField(this Type type, bool isClassicMono)
224-
=> !isClassicMono || type.SizeOfDefault() <= MonoDefaultCutoffSize;
225+
// ByRefLike and pointer cannot be used as generic arguments, so check for them before getting the size.
226+
=> !isClassicMono || type.IsByRefLike() || type.IsPointer || SizeOf(type) <= MonoDefaultCutoffSize;
225227

226-
private static int SizeOfDefault(this Type type) => type switch
228+
private static int SizeOf(Type type)
227229
{
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;
230+
return (int) GetGenericSizeOfMethod(type).Invoke(null, null);
279231
}
280232

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)
233+
private static MethodInfo GetGenericSizeOfMethod(Type skipInitType)
285234
{
286-
Math.DivRem(fieldsSize, IntPtr.Size, out int padding);
287-
return padding == 0
288-
? fieldsSize
289-
: fieldsSize - padding + IntPtr.Size;
235+
return typeof(Unsafe).GetMethods(BindingFlags.Static | BindingFlags.Public)
236+
.Single(m => m.Name == nameof(Unsafe.SizeOf) && m.IsGenericMethodDefinition && m.ReturnType == typeof(int) && m.GetParameters().Length == 0)
237+
.MakeGenericMethod(skipInitType);
290238
}
291239
}
292240
}

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

Lines changed: 1 addition & 1 deletion
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)