Skip to content

Commit 83452a4

Browse files
TurnerjRehanSaeed
authored andcommitted
Faster instance creation for ValuesJsonConverter (#119)
* Update benchmark to use .NET Core 3.1 * Use cached constructor delegate * Moved faster object activation to separate class
1 parent e41be22 commit 83452a4

File tree

4 files changed

+72
-5
lines changed

4 files changed

+72
-5
lines changed

Benchmarks/Schema.NET.Benchmarks/SchemaBenchmarkBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Schema.NET.Benchmarks
1313
[CsvMeasurementsExporter]
1414
[RPlotExporter]
1515
[SimpleJob(RuntimeMoniker.Net472)]
16-
[SimpleJob(RuntimeMoniker.NetCoreApp30)]
16+
[SimpleJob(RuntimeMoniker.NetCoreApp31)]
1717
public abstract class SchemaBenchmarkBase
1818
{
1919
protected Thing Thing { get; set; }

Source/Schema.NET/FastActivator.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
namespace Schema.NET
2+
{
3+
using System;
4+
using System.Collections.Concurrent;
5+
using System.Collections.Generic;
6+
using System.Linq.Expressions;
7+
using System.Reflection;
8+
using System.Text;
9+
10+
/// <summary>
11+
/// A faster version of <see cref="Activator.CreateInstance(System.Type, object[])"/> by providing constructor delegates.
12+
/// </summary>
13+
internal static class FastActivator
14+
{
15+
private static readonly ConcurrentDictionary<(Type, Type), Delegate> ConstructorDelegateLookup = new ConcurrentDictionary<(Type, Type), Delegate>();
16+
17+
/// <summary>
18+
/// Creates a constructor delegate for the specified type.
19+
/// </summary>
20+
/// <typeparam name="T1">Type of first argument for constructor.</typeparam>
21+
/// <param name="objectType">The object to find the constructor.</param>
22+
/// <returns>The constructor delegate.</returns>
23+
public static Func<T1, object> GetDynamicConstructor<T1>(Type objectType)
24+
{
25+
var constructorKey = (objectType, typeof(T1));
26+
if (!ConstructorDelegateLookup.TryGetValue(constructorKey, out var constructorDelegate))
27+
{
28+
var constructor = GetConstructorInfo(objectType, typeof(T1));
29+
constructorDelegate = CreateConstructorDelegate<T1>(constructor);
30+
ConstructorDelegateLookup.TryAdd(constructorKey, constructorDelegate);
31+
}
32+
33+
return constructorDelegate as Func<T1, object>;
34+
}
35+
36+
private static Func<T1, object> CreateConstructorDelegate<T1>(ConstructorInfo constructor) => Expression.Lambda<Func<T1, object>>(
37+
Expression.Convert(
38+
Expression.New(constructor, ConstructorParameter<T1>.SingleParameter),
39+
typeof(object)),
40+
ConstructorParameter<T1>.SingleParameter).Compile();
41+
42+
private static ConstructorInfo GetConstructorInfo(Type objectType, Type parameter1)
43+
{
44+
foreach (var constructor in objectType.GetTypeInfo().DeclaredConstructors)
45+
{
46+
var parameters = constructor.GetParameters();
47+
if (constructor.IsPublic && parameters.Length == 1 && parameters[0].ParameterType == parameter1)
48+
{
49+
return constructor;
50+
}
51+
}
52+
53+
return null;
54+
}
55+
56+
private static class ConstructorParameter<T1>
57+
{
58+
public static readonly ParameterExpression[] SingleParameter = new[] { Expression.Parameter(typeof(T1)) };
59+
}
60+
}
61+
}

Source/Schema.NET/Schema.NET.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
4747
<PackageReference Include="System.Memory" Version="4.5.3" />
4848
</ItemGroup>
49+
50+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.1' Or '$(TargetFramework)' == 'net461'">
51+
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
52+
</ItemGroup>
4953

5054
<ItemGroup Label="Analyzer Package References">
5155
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" PrivateAssets="all" Version="2.9.8" />

Source/Schema.NET/ValuesJsonConverter.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
namespace Schema.NET
22
{
33
using System;
4-
using System.Collections;
4+
using System.Collections.Concurrent;
55
using System.Collections.Generic;
66
using System.Diagnostics;
77
using System.Globalization;
8-
using System.Linq;
8+
using System.Linq.Expressions;
99
using System.Reflection;
1010
using System.Xml;
1111
using Newtonsoft.Json;
@@ -71,6 +71,8 @@ public override object ReadJson(
7171
throw new ArgumentNullException(nameof(serializer));
7272
}
7373

74+
var dynamicConstructor = FastActivator.GetDynamicConstructor<IEnumerable<object>>(objectType);
75+
7476
if (reader.TokenType == JsonToken.StartArray)
7577
{
7678
var items = new List<object>();
@@ -91,12 +93,12 @@ public override object ReadJson(
9193
items.Add(item);
9294
}
9395

94-
return Activator.CreateInstance(objectType, items);
96+
return dynamicConstructor(items);
9597
}
9698
else if (reader.TokenType != JsonToken.Null)
9799
{
98100
var item = ProcessToken(reader, objectType.GenericTypeArguments, serializer);
99-
return Activator.CreateInstance(objectType, (IEnumerable)new[] { item });
101+
return dynamicConstructor(new[] { item });
100102
}
101103

102104
return default;

0 commit comments

Comments
 (0)