Permalink
Browse files

better Value Types support

  • Loading branch information...
adamsitnik committed May 29, 2017
1 parent 165b134 commit afa803d0e38c0e11864b2e4394d4a85d3801d944
@@ -0,0 +1,75 @@
using System;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Jobs;
using BenchmarkDotNet.Environments;
namespace BenchmarkDotNet.Samples.Intro
{
// ReSharper disable InconsistentNaming
[RyuJitX64Job, LegacyJitX64Job, LegacyJitX86Job]
[MemoryDiagnoser]
public class IntroValueTypes
{
[Benchmark] public Jit ReturnEnum() => Jit.RyuJit;
[Benchmark] public DateTime ReturnDateTime() => new DateTime();
[Benchmark] public DateTime? ReturnNullableDateTime() => new DateTime();
[Benchmark] public int? ReturnNullableInt() => 0;
public struct StructWithReferencesOnly { public object _ref; }
[Benchmark] public StructWithReferencesOnly ReturnStructWithReferencesOnly() => new StructWithReferencesOnly();
public struct EmptyStruct { }
[Benchmark] public EmptyStruct ReturnEmptyStruct() => new EmptyStruct();
[Benchmark] public ValueTuple<int> ReturnGenericStructOfValueType() => new ValueTuple<int>(0);
[Benchmark] public ValueTuple<object> ReturnGenericStructOfReferenceType() => new ValueTuple<object>(null);
[Benchmark] public ValueTask<int> ReturnValueTaskOfValueType() => new ValueTask<int>(0);
[Benchmark] public ValueTask<object> ReturnValueTaskOfReferenceType() => new ValueTask<object>(result: null);
[Benchmark] public byte ReturnByte() => 0;
public struct Byte1 { public byte _1; }
[Benchmark] public Byte1 ReturnByte1() => new Byte1();
public struct Byte2 { public byte _1, _2; }
[Benchmark] public Byte2 ReturnByte2() => new Byte2();
public struct Byte3 { public byte _1, _2, _3; }
[Benchmark] public Byte3 ReturnByte3() => new Byte3();
public struct Byte4 { public byte _1, _2, _3, _4; }
[Benchmark] public Byte4 ReturnByte4() => new Byte4();
[Benchmark] public short ReturnShort() => 0;
public struct Short1 { public short _1; }
[Benchmark] public Short1 ReturnShort1() => new Short1();
public struct Short2 { public short _1, _2; }
[Benchmark] public Short2 ReturnShort2() => new Short2();
public struct Short3 { public short _1, _2, _3; }
[Benchmark] public Short3 ReturnShort3() => new Short3();
public struct Short4 { public short _1, _2, _3, _4; }
[Benchmark] public Short4 ReturnShort4() => new Short4();
[Benchmark] public int ReturnInt() => 0;
public struct Int1 { public int _1; }
[Benchmark] public Int1 ReturnInt1() => new Int1();
public struct Int2 { public int _1, _2; }
[Benchmark] public Int2 ReturnInt2() => new Int2();
public struct Int3 { public int _1, _2, _3; }
[Benchmark] public Int3 ReturnInt3() => new Int3();
public struct Int4 { public int _1, _2, _3, _4; }
[Benchmark] public Int4 ReturnInt4() => new Int4();
[Benchmark] public long ReturnLong() => 0;
public struct Long1 { public long _1; }
[Benchmark] public Long1 ReturnLong1() => new Long1();
public struct Long2 { public long _1, _2; }
[Benchmark] public Long2 ReturnLong2() => new Long2();
public struct Long3 { public long _1, _2, _3; }
[Benchmark] public Long3 ReturnLong3() => new Long3();
public struct Long4 { public long _1, _2, _3, _4; }
[Benchmark] public Long4 ReturnLong4() => new Long4();
}
// ReSharper restore InconsistentNaming
}
@@ -31,20 +31,22 @@ internal static string Generate(Benchmark benchmark)
Replace("$TargetTypeName$", provider.TargetTypeName).
Replace("$TargetMethodDelegate$", provider.TargetMethodDelegate).
Replace("$TargetMethodDelegateType$", provider.TargetMethodDelegateType).
Replace("$TargetMethodReturnType$", provider.TargetMethodReturnTypeName).
Replace("$IdleMethodDelegateType$", provider.IdleMethodDelegateType).
Replace("$IdleMethodReturnType$", provider.IdleMethodReturnType).
Replace("$IdleMethodReturnType$", provider.IdleMethodReturnTypeName).
Replace("$GlobalSetupMethodName$", provider.GlobalSetupMethodName).
Replace("$GlobalCleanupMethodName$", provider.GlobalCleanupMethodName).
Replace("$IterationSetupMethodName$", provider.IterationSetupMethodName).
Replace("$IterationCleanupMethodName$", provider.IterationCleanupMethodName).
Replace("$IdleImplementation$", provider.IdleImplementation).
Replace("$HasReturnValue$", provider.HasReturnValue).
Replace("$ExtraDefines$", provider.ExtraDefines).
Replace("$ConsumeField$", provider.ConsumeField).
Replace("$AdditionalLogic$", benchmark.Target.AdditionalLogic).
Replace("$JobSetDefinition$", GetJobsSetDefinition(benchmark)).
Replace("$ParamsContent$", GetParamsContent(benchmark)).
Replace("$ExtraAttribute$", GetExtraAttributes(benchmark.Target)).
Replace("$EngineFactoryType$", GetEngineFactoryTypeName(benchmark)).
Replace("$ShadowCopyDefines$", useShadowCopy ? "#define SHADOWCOPY" : string.Empty).
Replace("$ShadowCopyDefines$", useShadowCopy ? "#define SHADOWCOPY" : null).
Replace("$ShadowCopyFolderPath$", shadowCopyFolderPath).
ToString();
@@ -3,8 +3,9 @@
using System.Reflection;
using System.Threading.Tasks;
using BenchmarkDotNet.Core.Helpers;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Extensions;
namespace BenchmarkDotNet.Code
{
@@ -34,19 +35,27 @@ internal DeclarationsProvider(Target target)
public string IterationCleanupMethodName => Target.IterationCleanupMethod?.Name ?? EmptyAction;
public virtual string TargetMethodDelegate => Target.Method.Name;
public virtual string ExtraDefines => null;
public abstract string TargetMethodReturnTypeNamespace { get; }
protected virtual Type TargetMethodReturnType => Target.Method.ReturnType;
public string TargetMethodReturnTypeName => TargetMethodReturnType.GetCorrectTypeName();
public abstract string TargetMethodDelegateType { get; }
public abstract string IdleMethodReturnType { get; }
public virtual string TargetMethodDelegate => Target.Method.Name;
public virtual string ConsumeField => null;
protected abstract Type IdleMethodReturnType { get; }
public string IdleMethodReturnTypeName => IdleMethodReturnType.GetCorrectTypeName();
public abstract string IdleMethodDelegateType { get; }
public abstract string IdleImplementation { get; }
public abstract string HasReturnValue { get; }
}
internal class VoidDeclarationsProvider : DeclarationsProvider
@@ -57,13 +66,11 @@ internal class VoidDeclarationsProvider : DeclarationsProvider
public override string TargetMethodDelegateType => "Action";
public override string IdleMethodReturnType => "void";
protected override Type IdleMethodReturnType => typeof(void);
public override string IdleMethodDelegateType => TargetMethodDelegateType;
public override string IdleImplementation => string.Empty;
public override string HasReturnValue => "false";
}
internal class NonVoidDeclarationsProvider : DeclarationsProvider
@@ -76,30 +83,30 @@ public override string TargetMethodReturnTypeNamespace
? string.Empty
: $"using {TargetMethodReturnType.Namespace};";
public virtual Type TargetMethodReturnType => Target.Method.ReturnType;
public string TargetMethodReturnTypeName => TargetMethodReturnType.GetCorrectTypeName();
public override string TargetMethodDelegateType => $"Func<{TargetMethodReturnTypeName}>";
public override string IdleMethodReturnType
=> TargetMethodReturnType.IsStruct()
? "int" // we return int because creating bigger ValueType could take longer than benchmarked method itself
: TargetMethodReturnTypeName;
public override string ConsumeField
=> !Consumer.IsConsumable(TargetMethodReturnType) && Consumer.HasConsumableField(TargetMethodReturnType, out var field)
? $".{field.Name}"
: null;
protected override Type IdleMethodReturnType
=> Consumer.IsConsumable(TargetMethodReturnType)
? TargetMethodReturnType
: (Consumer.HasConsumableField(TargetMethodReturnType, out var field)
? field.FieldType
: typeof(int)); // we return this simple type because creating bigger ValueType could take longer than benchmarked method itself
public override string IdleMethodDelegateType
=> TargetMethodReturnType.IsStruct()
? "Func<int>"
: $"Func<{TargetMethodReturnTypeName}>";
public override string IdleMethodDelegateType => $"Func<{IdleMethodReturnTypeName}>";
public override string IdleImplementation
{
get
{
string value;
var type = TargetMethodReturnType;
if (type.IsStruct())
value = "0";
var type = IdleMethodReturnType;
if (type.GetTypeInfo().IsPrimitive)
value = $"default({type.GetCorrectTypeName()})";
else if (type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface)
value = "null";
else
@@ -108,7 +115,10 @@ public override string IdleImplementation
}
}
public override string HasReturnValue => "true";
public override string ExtraDefines
=> Consumer.IsConsumable(TargetMethodReturnType) || Consumer.HasConsumableField(TargetMethodReturnType, out var _)
? "#define RETURNS_CONSUMABLE"
: "#define RETURNS_NON_CONSUMABLE_STRUCT";
}
internal class TaskDeclarationsProvider : VoidDeclarationsProvider
@@ -130,7 +140,7 @@ public GenericTaskDeclarationsProvider(Target target) : base(target)
{
}
public override Type TargetMethodReturnType => Target.Method.ReturnType.GetTypeInfo().GetGenericArguments().Single();
protected override Type TargetMethodReturnType => Target.Method.ReturnType.GetTypeInfo().GetGenericArguments().Single();
// we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way,
// and will eventually throw actual exception, not aggregated one
@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -7,6 +10,13 @@ namespace BenchmarkDotNet.Engines
{
public class Consumer
{
private static readonly HashSet<Type> SupportedTypes
= new HashSet<Type>(
typeof(Consumer).GetTypeInfo()
.DeclaredFields
.Where(field => !field.IsStatic) // exclude this HashSet itself
.Select(field => field.FieldType));
private volatile byte byteHolder;
private volatile sbyte sbyteHolder;
private volatile short shortHolder;
@@ -69,6 +79,30 @@ public class Consumer
public void Consume(object objectValue) => Volatile.Write(ref objectHolder, objectValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Consume<T>(T objectValue) => Volatile.Write(ref objectHolder, objectValue);
public void Consume<T>(T objectValue) where T : class // class constraint prevents from boxing structs
=> Volatile.Write(ref objectHolder, objectValue);
internal static bool IsConsumable(Type type)
=> SupportedTypes.Contains(type) || type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface;
internal static bool HasConsumableField(Type type, out FieldInfo consumableField)
{
var typeInfo = type.GetTypeInfo();
if (typeInfo.IsEnum)
{
// Enums are tricky bastards which report "value__" field, which is public for reflection, but inaccessible via C#
consumableField = null;
return false;
}
var publicInstanceFields = typeInfo.DeclaredFields
.Where(field => field.IsPublic && !field.IsStatic)
.ToArray();
consumableField = publicInstanceFields.FirstOrDefault(field => IsConsumable(field.FieldType));
return consumableField != null;
}
}
}
@@ -0,0 +1,14 @@
using System.Runtime.CompilerServices;
namespace BenchmarkDotNet.Engines
{
public static class DeadCodeEliminationHelper
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static void KeepAliveWithoutBoxing<T>(T value)
{
// this method can't get inlined, so any value send to it
// will not get eliminated by the dead code elimination
}
}
}
@@ -1,4 +1,5 @@
$ShadowCopyDefines$
$ExtraDefines$
using System;
using System.Diagnostics;
using System.Linq;
@@ -175,7 +176,7 @@ namespace BenchmarkDotNet.Autogenerated
$IdleImplementation$
}
#if $HasReturnValue$
#if RETURNS_CONSUMABLE
private Consumer consumer = new Consumer();
@@ -191,10 +192,32 @@ namespace BenchmarkDotNet.Autogenerated
{
for (long i = 0; i < invokeCount; i++)
{
consumer.Consume(mainAction());@Unroll@
consumer.Consume(mainAction()$ConsumeField$);@Unroll@
}
}
#elif RETURNS_NON_CONSUMABLE_STRUCT
private void IdleMultiAction(long invokeCount)
{
$IdleMethodReturnType$ result = default($IdleMethodReturnType$);
for (long i = 0; i < invokeCount; i++)
{
result = idleAction();@Unroll@
}
DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result);
}
private void MainMultiAction(long invokeCount)
{
$TargetMethodReturnType$ result = default($TargetMethodReturnType$);
for (long i = 0; i < invokeCount; i++)
{
result = mainAction();@Unroll@
}
DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result);
}
#else
private void IdleMultiAction(long invokeCount)
@@ -101,6 +101,20 @@ public void EngineShouldNotInterfereAllocationResults(IToolchain toolchain)
});
}
public class NoBoxing
{
[Benchmark] public ValueTuple<int> ReturnsValueType() => new ValueTuple<int>(0);
}
[Theory, MemberData(nameof(GetToolchains))]
public void EngineShouldNotIntroduceBoxing(IToolchain toolchain)
{
AssertAllocations(toolchain, typeof(NoBoxing), new Dictionary<string, long>
{
{ nameof(NoBoxing.ReturnsValueType), 0 }
});
}
public class NonAllocatingAsynchronousBenchmarks
{
private readonly Task<int> completedTaskOfT = Task.FromResult(default(int)); // we store it in the field, because Task<T> is reference type so creating it allocates heap memory
@@ -1,5 +1,6 @@
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Environments;
using Xunit;
using Xunit.Abstractions;
@@ -48,6 +49,12 @@ public Diagnostics.Windows.InliningDiagnoser TypeFromCustomDependency()
[Benchmark]
public DateTime ReturnNonDefaultValueForValueType() => DateTime.UtcNow;
[Benchmark]
public ValueTuple<DateTime> ReturnGenericValueType() => new ValueTuple<DateTime>();
[Benchmark]
public Jit ReturnEnum() => Jit.RyuJit;
}
}
}

0 comments on commit afa803d

Please sign in to comment.